@pellux/goodvibes-agent 1.0.14 → 1.0.15

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/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  Product-facing release notes for GoodVibes Agent.
4
4
 
5
+ ## 1.0.15 - 2026-06-03
6
+
7
+ - Add forgiving agent_harness slash-command detail lookup by command, commandName, target, or query with parsed invocation metadata.
8
+ - Keep slash-command catalogs lightweight while making one-command inspection work from typed user-style invocations, aliases, case-insensitive roots, and unique description matches.
9
+ - Refresh package-facing docs and focused harness coverage for the updated model-visible slash-command inspection path.
10
+
5
11
  ## 1.0.14 - 2026-06-03
6
12
 
7
13
  - Add agent_harness mode tool to inspect one first-class model tool schema by toolName, target, or query.
package/README.md CHANGED
@@ -7,7 +7,7 @@ GoodVibes Agent is the personal operator assistant TUI for GoodVibes. It is buil
7
7
 
8
8
  The Agent product connects to a GoodVibes host owned outside this package. It does not install, start, stop, restart, or own that host.
9
9
 
10
- Most work happens in the interactive TUI. The installed CLI exists to launch that TUI and provide scriptable mirrors for workflows that are already reachable from the workspace. The main Agent model can inspect the same harness surface through Agent-owned tools: workspace actions, built-in panels, modal/overlay/picker UI surfaces, named operator surfaces, top-level CLI mirrors, fixed shortcuts, configurable keybindings, slash commands, command policy metadata, settings, model tools and individual model tool schemas, local registries, connected-host capability boundaries, and live connected-host readiness are all discoverable from the conversation without exposing connected-host lifecycle control.
10
+ Most work happens in the interactive TUI. The installed CLI exists to launch that TUI and provide scriptable mirrors for workflows that are already reachable from the workspace. The main Agent model can inspect the same harness surface through Agent-owned tools: workspace actions, built-in panels, modal/overlay/picker UI surfaces, named operator surfaces, top-level CLI mirrors, fixed shortcuts, configurable keybindings, slash commands, single-command lookup with policy metadata, settings, model tools and individual model tool schemas, local registries, connected-host capability boundaries, and live connected-host readiness are all discoverable from the conversation without exposing connected-host lifecycle control.
11
11
 
12
12
  ## Install
13
13
 
@@ -66,7 +66,7 @@ Use the workspace as the primary product surface:
66
66
 
67
67
  Inside the Agent workspace, press `/` to search every workspace action by name, category, command, or detail. This is the primary discovery path for product actions; slash commands and CLI subcommands remain power-user/scriptable mirrors.
68
68
 
69
- The model has the same harness map through `agent_harness`: it can list Agent workspace actions, list built-in panels and their workspace routes, inspect modal/overlay/picker UI surfaces, list top-level CLI mirrors, inspect fixed shortcuts and configurable keybindings, list slash commands, inspect command policy metadata, inspect model tool definitions or one model tool schema, inspect or change Agent settings with confirmation, run concrete workspace or slash-command mirrors with confirmation, and report connected-host capability and live readiness posture. CLI mirror modes are read-only catalog and parser inspection; inside the main conversation, the model uses the returned preferred model tool, workspace action, setting mode, or confirmed slash-command mirror instead of launching hidden nested CLI processes. Panel modes expose catalog/open state and route visible panel/workspace changes through the current Agent shell bridge with confirmation. UI surface modes expose help, shortcuts, command browser, conversation search, prompt-history search, slash-command mode, file picker, block actions, context, runtime activity, live process output, bookmarks, model/provider/reasoning-effort pickers, TTS provider/voice pickers, session/profile pickers, the panel-picker compatibility route, security/knowledge/subscription operator surfaces, settings, MCP workspace, onboarding, and Agent workspace entrypoints; `open_ui_surface` is confirmation-gated and only performs visible shell navigation. Shortcut modes expose fixed runtime/editor controls plus the live resolved keybinding table; `set_keybinding` and `reset_keybinding` write the same `keybindings.json` file the user edits and require confirmation. Workspace action discovery can inline editor field schemas with `includeParameters:true`; profile editors use the current starter-template inventory, and routine schedule editors can prefill a selected local routine from `recordId`. Selection-based local workspace actions accept a local `recordId` so the model can use the same note promotion and local registry flows as the TUI. Direct local create editors for memory, notes, personas, skills, and routines can execute from submitted fields through `run_workspace_action` and `agent_local_registry` with confirmation. The `connected_host` report includes route families, allowed capabilities, blocked lifecycle/non-Agent surfaces, and availability for the first-class Agent tools; `connected_host_capability` inspects one allowed or blocked capability by id, target, or query with its route families and boundary. The `connected_host_status` report performs a read-only live check of the connected-host status route and the isolated Agent Knowledge status route, reports SDK compatibility, token posture, endpoint bindings, route readiness, and actionable findings, and still refuses connected-host lifecycle control. The model uses those first-class tools for product workflows where they exist, such as Agent Knowledge, local memory/notes/personas/skills/routines, channel sends, notifications, reminders, media generation, work plans, and connected-host operator actions.
69
+ The model has the same harness map through `agent_harness`: it can list Agent workspace actions, list built-in panels and their workspace routes, inspect modal/overlay/picker UI surfaces, list top-level CLI mirrors, inspect fixed shortcuts and configurable keybindings, list slash commands, inspect one slash command by `command`, `commandName`, `target`, or `query` with parsed arguments and policy metadata, inspect model tool definitions or one model tool schema, inspect or change Agent settings with confirmation, run concrete workspace or slash-command mirrors with confirmation, and report connected-host capability and live readiness posture. CLI mirror modes are read-only catalog and parser inspection; inside the main conversation, the model uses the returned preferred model tool, workspace action, setting mode, or confirmed slash-command mirror instead of launching hidden nested CLI processes. Panel modes expose catalog/open state and route visible panel/workspace changes through the current Agent shell bridge with confirmation. UI surface modes expose help, shortcuts, command browser, conversation search, prompt-history search, slash-command mode, file picker, block actions, context, runtime activity, live process output, bookmarks, model/provider/reasoning-effort pickers, TTS provider/voice pickers, session/profile pickers, the panel-picker compatibility route, security/knowledge/subscription operator surfaces, settings, MCP workspace, onboarding, and Agent workspace entrypoints; `open_ui_surface` is confirmation-gated and only performs visible shell navigation. Shortcut modes expose fixed runtime/editor controls plus the live resolved keybinding table; `set_keybinding` and `reset_keybinding` write the same `keybindings.json` file the user edits and require confirmation. Workspace action discovery can inline editor field schemas with `includeParameters:true`; profile editors use the current starter-template inventory, and routine schedule editors can prefill a selected local routine from `recordId`. Selection-based local workspace actions accept a local `recordId` so the model can use the same note promotion and local registry flows as the TUI. Direct local create editors for memory, notes, personas, skills, and routines can execute from submitted fields through `run_workspace_action` and `agent_local_registry` with confirmation. The `connected_host` report includes route families, allowed capabilities, blocked lifecycle/non-Agent surfaces, and availability for the first-class Agent tools; `connected_host_capability` inspects one allowed or blocked capability by id, target, or query with its route families and boundary. The `connected_host_status` report performs a read-only live check of the connected-host status route and the isolated Agent Knowledge status route, reports SDK compatibility, token posture, endpoint bindings, route readiness, and actionable findings, and still refuses connected-host lifecycle control. The model uses those first-class tools for product workflows where they exist, such as Agent Knowledge, local memory/notes/personas/skills/routines, channel sends, notifications, reminders, media generation, work plans, and connected-host operator actions.
70
70
 
71
71
  The setup workspace surfaces discovered Agent-local persona, skill, and routine markdown files so day-one setup can import useful behavior instead of starting from blank records. It can also create one initial scratchpad note, local persona, skill, and routine directly during first-run setup; those records stay in Agent-local registries and never write to default knowledge or non-Agent segments.
72
72
 
@@ -816566,7 +816566,7 @@ var createStyledCell = (char, overrides = {}) => ({
816566
816566
  // src/version.ts
816567
816567
  import { readFileSync } from "fs";
816568
816568
  import { join } from "path";
816569
- var _version = "1.0.14";
816569
+ var _version = "1.0.15";
816570
816570
  var _sdkVersion = "0.33.35";
816571
816571
  try {
816572
816572
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, "..", "package.json"), "utf-8"));
@@ -847775,13 +847775,13 @@ init_state3();
847775
847775
 
847776
847776
  // src/input/commands/recall-shared.ts
847777
847777
  var VALID_CLASSES = ["decision", "constraint", "incident", "pattern", "fact", "risk", "runbook", "architecture", "ownership"];
847778
- var VALID_SCOPES = ["session", "project", "team"];
847778
+ var VALID_SCOPES2 = ["session", "project", "team"];
847779
847779
  var VALID_REVIEW_STATES = ["fresh", "reviewed", "stale", "contradicted"];
847780
847780
  function isValidClass(s4) {
847781
847781
  return VALID_CLASSES.includes(s4);
847782
847782
  }
847783
847783
  function isValidScope(s4) {
847784
- return VALID_SCOPES.includes(s4);
847784
+ return VALID_SCOPES2.includes(s4);
847785
847785
  }
847786
847786
  function isValidReviewState(s4) {
847787
847787
  return VALID_REVIEW_STATES.includes(s4);
@@ -847824,7 +847824,7 @@ function handleRecallSearch(args2, context) {
847824
847824
  if (isValidScope(scope))
847825
847825
  filter.scope = scope;
847826
847826
  else {
847827
- context.print(`[memory] Unknown scope "${scope}". Valid values ${VALID_SCOPES.join(", ")}.`);
847827
+ context.print(`[memory] Unknown scope "${scope}". Valid values ${VALID_SCOPES2.join(", ")}.`);
847828
847828
  return;
847829
847829
  }
847830
847830
  }
@@ -847999,7 +847999,7 @@ function handleRecallList(args2, context) {
847999
847999
  if (scopeIdx !== -1 && args2[scopeIdx + 1]) {
848000
848000
  const scope = args2[scopeIdx + 1];
848001
848001
  if (!isValidScope(scope)) {
848002
- context.print(`[memory] Unknown scope "${scope}". Valid values ${VALID_SCOPES.join(", ")}.`);
848002
+ context.print(`[memory] Unknown scope "${scope}". Valid values ${VALID_SCOPES2.join(", ")}.`);
848003
848003
  return;
848004
848004
  }
848005
848005
  filter.scope = scope;
@@ -848049,7 +848049,7 @@ async function handleRecallAdd(args2, context) {
848049
848049
  const tags = tagsRaw ? tagsRaw.split(",").map((token) => token.trim()).filter(Boolean) : [];
848050
848050
  const scope = scopeRaw && isValidScope(scopeRaw) ? scopeRaw : "project";
848051
848051
  if (scopeRaw && !isValidScope(scopeRaw)) {
848052
- context.print(`[memory] Invalid scope "${scopeRaw}". Valid values ${VALID_SCOPES.join(", ")}.`);
848052
+ context.print(`[memory] Invalid scope "${scopeRaw}". Valid values ${VALID_SCOPES2.join(", ")}.`);
848053
848053
  return;
848054
848054
  }
848055
848055
  const provenance = [];
@@ -848183,7 +848183,7 @@ function handleRecallExport(args2, context) {
848183
848183
  if (scopeIdx !== -1 && commandArgs[scopeIdx + 1]) {
848184
848184
  const scope = commandArgs[scopeIdx + 1];
848185
848185
  if (!isValidScope(scope)) {
848186
- context.print(`[memory] Unknown scope "${scope}". Valid values ${VALID_SCOPES.join(", ")}.`);
848186
+ context.print(`[memory] Unknown scope "${scope}". Valid values ${VALID_SCOPES2.join(", ")}.`);
848187
848187
  return;
848188
848188
  }
848189
848189
  filter.scope = scope;
@@ -848262,7 +848262,7 @@ function handleRecallHandoffExport(args2, context) {
848262
848262
  const scopeIdx = commandArgs.indexOf("--scope");
848263
848263
  const scopeRaw = scopeIdx !== -1 ? commandArgs[scopeIdx + 1] : "team";
848264
848264
  if (!scopeRaw || !isValidScope(scopeRaw)) {
848265
- context.print(`[memory] Unknown scope "${scopeRaw ?? ""}". Valid values ${VALID_SCOPES.join(", ")}.`);
848265
+ context.print(`[memory] Unknown scope "${scopeRaw ?? ""}". Valid values ${VALID_SCOPES2.join(", ")}.`);
848266
848266
  return;
848267
848267
  }
848268
848268
  const bundle = memory.exportBundle({ scope: scopeRaw });
@@ -848378,7 +848378,7 @@ function handleRecallPromote(args2, context) {
848378
848378
  const id = parsed.rest[0];
848379
848379
  const scope = parsed.rest[1];
848380
848380
  if (!id || !scope || !isValidScope(scope)) {
848381
- context.print(`[memory] Usage: /memory promote <id> <${VALID_SCOPES.join("|")}> --yes`);
848381
+ context.print(`[memory] Usage: /memory promote <id> <${VALID_SCOPES2.join("|")}> --yes`);
848382
848382
  return;
848383
848383
  }
848384
848384
  if (!parsed.yes) {
@@ -862223,7 +862223,7 @@ import { mkdirSync as mkdirSync64, readFileSync as readFileSync86, writeFileSync
862223
862223
  import { dirname as dirname63, resolve as resolve39 } from "path";
862224
862224
  init_state3();
862225
862225
  var VALID_CLASSES2 = ["decision", "constraint", "incident", "pattern", "fact", "risk", "runbook", "architecture", "ownership"];
862226
- var VALID_SCOPES3 = ["session", "project", "team"];
862226
+ var VALID_SCOPES4 = ["session", "project", "team"];
862227
862227
  var VALID_REVIEW_STATES3 = ["fresh", "reviewed", "stale", "contradicted"];
862228
862228
  var VALID_PROVENANCE_KINDS = ["session", "turn", "task", "event", "file"];
862229
862229
  var VALUE_OPTIONS = new Set([
@@ -862323,7 +862323,7 @@ function isMemoryClass2(value) {
862323
862323
  return VALID_CLASSES2.includes(value);
862324
862324
  }
862325
862325
  function isMemoryScope2(value) {
862326
- return VALID_SCOPES3.includes(value);
862326
+ return VALID_SCOPES4.includes(value);
862327
862327
  }
862328
862328
  function isReviewState(value) {
862329
862329
  return VALID_REVIEW_STATES3.includes(value);
@@ -862338,7 +862338,7 @@ function requireClass(value) {
862338
862338
  }
862339
862339
  function requireScope(value) {
862340
862340
  if (!value || !isMemoryScope2(value))
862341
- throw new Error(`Invalid memory scope "${value ?? ""}". Valid values ${VALID_SCOPES3.join(", ")}`);
862341
+ throw new Error(`Invalid memory scope "${value ?? ""}". Valid values ${VALID_SCOPES4.join(", ")}`);
862342
862342
  return value;
862343
862343
  }
862344
862344
  function optionalScope(value) {
@@ -879834,6 +879834,126 @@ function describeHarnessCliCommand(args2) {
879834
879834
  };
879835
879835
  }
879836
879836
 
879837
+ // src/tools/agent-harness-command-catalog.ts
879838
+ function readString51(value) {
879839
+ return typeof value === "string" ? value.trim() : "";
879840
+ }
879841
+ function readLimit6(value, fallback) {
879842
+ const parsed = typeof value === "string" && value.trim() ? Number(value) : value;
879843
+ if (typeof parsed !== "number" || !Number.isFinite(parsed))
879844
+ return fallback;
879845
+ return Math.max(1, Math.min(500, Math.trunc(parsed)));
879846
+ }
879847
+ function readStringArray12(value) {
879848
+ if (!Array.isArray(value))
879849
+ return [];
879850
+ return value.map((entry) => typeof entry === "string" ? entry : String(entry));
879851
+ }
879852
+ function commandMatches(command8, query2) {
879853
+ if (!query2)
879854
+ return true;
879855
+ const haystack = [
879856
+ command8.name,
879857
+ ...command8.aliases ?? [],
879858
+ command8.description,
879859
+ command8.usage ?? "",
879860
+ command8.argsHint ?? ""
879861
+ ].join(`
879862
+ `).toLowerCase();
879863
+ return haystack.includes(query2.toLowerCase());
879864
+ }
879865
+ function describeCommand(command8, lookup) {
879866
+ return {
879867
+ name: command8.name,
879868
+ slash: `/${command8.name}`,
879869
+ aliases: command8.aliases ?? [],
879870
+ description: command8.description,
879871
+ usage: command8.usage ?? "",
879872
+ argsHint: command8.argsHint ?? command8.usage ?? "",
879873
+ ...lookup ? {
879874
+ lookup: {
879875
+ source: lookup.source,
879876
+ input: lookup.input,
879877
+ parsedName: lookup.parsedName,
879878
+ parsedArgs: lookup.parsedArgs,
879879
+ resolvedBy: lookup.resolvedBy
879880
+ }
879881
+ } : {},
879882
+ policy: describeCommandPolicy(command8.name)
879883
+ };
879884
+ }
879885
+ function commandDetailLookupFromArgs(args2) {
879886
+ const rawCommand = readString51(args2.command);
879887
+ if (rawCommand) {
879888
+ const parsed = parseSlashCommand(rawCommand);
879889
+ return { source: "command", input: rawCommand, parsedName: parsed.name, parsedArgs: parsed.args };
879890
+ }
879891
+ const rawCommandName = readString51(args2.commandName);
879892
+ if (rawCommandName) {
879893
+ const parsed = parseSlashCommand(rawCommandName);
879894
+ const explicitArgs = readStringArray12(args2.args);
879895
+ return {
879896
+ source: "commandName",
879897
+ input: rawCommandName,
879898
+ parsedName: parsed.name,
879899
+ parsedArgs: explicitArgs.length > 0 ? explicitArgs : parsed.args
879900
+ };
879901
+ }
879902
+ const rawTarget = readString51(args2.target);
879903
+ if (rawTarget) {
879904
+ const parsed = parseSlashCommand(rawTarget);
879905
+ return { source: "target", input: rawTarget, parsedName: parsed.name, parsedArgs: parsed.args };
879906
+ }
879907
+ const rawQuery = readString51(args2.query);
879908
+ if (rawQuery) {
879909
+ const parsed = parseSlashCommand(rawQuery);
879910
+ return { source: "query", input: rawQuery, parsedName: parsed.name, parsedArgs: parsed.args };
879911
+ }
879912
+ return null;
879913
+ }
879914
+ function resolveCommandDetail(commandRegistry, args2) {
879915
+ const lookup = commandDetailLookupFromArgs(args2);
879916
+ if (!lookup?.parsedName)
879917
+ return null;
879918
+ const direct = commandRegistry.get(lookup.parsedName);
879919
+ if (direct) {
879920
+ return {
879921
+ command: direct,
879922
+ lookup: {
879923
+ ...lookup,
879924
+ resolvedBy: direct.name === lookup.parsedName ? "name" : "alias"
879925
+ }
879926
+ };
879927
+ }
879928
+ const normalized = lookup.parsedName.toLowerCase();
879929
+ for (const command8 of commandRegistry.list()) {
879930
+ if (command8.name.toLowerCase() === normalized) {
879931
+ return { command: command8, lookup: { ...lookup, resolvedBy: "case-insensitive-name" } };
879932
+ }
879933
+ if ((command8.aliases ?? []).some((alias) => alias.toLowerCase() === normalized)) {
879934
+ return { command: command8, lookup: { ...lookup, resolvedBy: "case-insensitive-alias" } };
879935
+ }
879936
+ }
879937
+ const descriptionMatches = commandRegistry.list().filter((command8) => commandMatches(command8, lookup.input));
879938
+ if (descriptionMatches.length === 1) {
879939
+ return { command: descriptionMatches[0], lookup: { ...lookup, resolvedBy: "description" } };
879940
+ }
879941
+ const prefixMatches = commandRegistry.fuzzyMatch(lookup.parsedName).filter((entry) => entry.score >= 80);
879942
+ if (prefixMatches.length === 1) {
879943
+ return { command: prefixMatches[0].command, lookup: { ...lookup, resolvedBy: "prefix" } };
879944
+ }
879945
+ return null;
879946
+ }
879947
+ function listHarnessCommands(commandRegistry, args2) {
879948
+ const query2 = readString51(args2.query);
879949
+ const limit3 = readLimit6(args2.limit, 200);
879950
+ return commandRegistry.list().filter((command8) => commandMatches(command8, query2)).sort((a4, b3) => a4.name.localeCompare(b3.name)).slice(0, limit3).map((command8) => describeCommand(command8));
879951
+ }
879952
+ function describeHarnessCommand(commandRegistry, args2) {
879953
+ const detail = resolveCommandDetail(commandRegistry, args2);
879954
+ return detail ? describeCommand(detail.command, detail.lookup) : null;
879955
+ }
879956
+
879837
879957
  // src/tools/agent-harness-keybinding-metadata.ts
879838
879958
  import { existsSync as existsSync82, mkdirSync as mkdirSync67, readFileSync as readFileSync89, writeFileSync as writeFileSync58 } from "fs";
879839
879959
  import { dirname as dirname66 } from "path";
@@ -879847,10 +879967,10 @@ var FIXED_SHORTCUTS = [
879847
879967
  { key: "F2 / /shortcuts", description: "Open keyboard shortcut reference" },
879848
879968
  { key: "/keybindings", description: "List configurable keybindings and config path" }
879849
879969
  ];
879850
- function readString51(value) {
879970
+ function readString52(value) {
879851
879971
  return typeof value === "string" ? value.trim() : "";
879852
879972
  }
879853
- function readLimit6(value, fallback) {
879973
+ function readLimit7(value, fallback) {
879854
879974
  const parsed = typeof value === "string" && value.trim() ? Number(value) : value;
879855
879975
  if (typeof parsed !== "number" || !Number.isFinite(parsed))
879856
879976
  return fallback;
@@ -879871,7 +879991,7 @@ function isKeyAction2(action2) {
879871
879991
  return Object.hasOwn(ACTION_DESCRIPTIONS, action2);
879872
879992
  }
879873
879993
  function actionFromArgs(args2) {
879874
- const action2 = readString51(args2.actionId || args2.key || args2.query);
879994
+ const action2 = readString52(args2.actionId || args2.key || args2.query);
879875
879995
  return action2 && isKeyAction2(action2) ? action2 : null;
879876
879996
  }
879877
879997
  function readBoolean15(value) {
@@ -879890,7 +880010,7 @@ function normalizeCombo(value) {
879890
880010
  if (typeof value !== "object" || value === null || Array.isArray(value))
879891
880011
  return null;
879892
880012
  const raw = value;
879893
- const key = readString51(raw.key);
880013
+ const key = readString52(raw.key);
879894
880014
  if (!key)
879895
880015
  return null;
879896
880016
  const combo = { key };
@@ -879940,7 +880060,7 @@ function combosFromArgs(args2) {
879940
880060
  const fieldCombo = normalizeCombo(fields);
879941
880061
  if (fieldCombo)
879942
880062
  return [fieldCombo];
879943
- const labelCombo = parseComboLabel(readString51(args2.value));
880063
+ const labelCombo = parseComboLabel(readString52(args2.value));
879944
880064
  if (labelCombo)
879945
880065
  return [labelCombo];
879946
880066
  throw new Error('set_keybinding requires combo, combos, fields.key, or a value such as "Ctrl+G".');
@@ -880008,9 +880128,9 @@ function totalHarnessShortcuts(context) {
880008
880128
  }
880009
880129
  function listHarnessShortcuts(context, args2) {
880010
880130
  const keybindings = listHarnessKeybindings(context, args2);
880011
- const query2 = readString51(args2.query).toLowerCase();
880131
+ const query2 = readString52(args2.query).toLowerCase();
880012
880132
  const fixed = FIXED_SHORTCUTS.filter((shortcut) => !query2 || `${shortcut.key}
880013
- ${shortcut.description}`.toLowerCase().includes(query2)).slice(0, readLimit6(args2.limit, 200)).map((shortcut) => ({ ...shortcut, source: "fixed", userEditable: false }));
880133
+ ${shortcut.description}`.toLowerCase().includes(query2)).slice(0, readLimit7(args2.limit, 200)).map((shortcut) => ({ ...shortcut, source: "fixed", userEditable: false }));
880014
880134
  return {
880015
880135
  configPath: keybindings.configPath,
880016
880136
  fixedShortcuts: fixed,
@@ -880022,8 +880142,8 @@ ${shortcut.description}`.toLowerCase().includes(query2)).slice(0, readLimit6(arg
880022
880142
  }
880023
880143
  function listHarnessKeybindings(context, args2) {
880024
880144
  const manager5 = requireKeybindingsManager2(context);
880025
- const query2 = readString51(args2.query);
880026
- const entries = manager5.getAll().filter((entry) => bindingMatches(entry, query2)).slice(0, readLimit6(args2.limit, 200)).map((entry) => describeBinding(manager5, entry.action, entry.combos));
880145
+ const query2 = readString52(args2.query);
880146
+ const entries = manager5.getAll().filter((entry) => bindingMatches(entry, query2)).slice(0, readLimit7(args2.limit, 200)).map((entry) => describeBinding(manager5, entry.action, entry.combos));
880027
880147
  return {
880028
880148
  configPath: manager5.getConfigPath(),
880029
880149
  keybindings: entries,
@@ -880078,10 +880198,10 @@ function resetHarnessKeybinding(context, args2) {
880078
880198
  }
880079
880199
 
880080
880200
  // src/tools/agent-harness-panel-metadata.ts
880081
- function readString52(value) {
880201
+ function readString53(value) {
880082
880202
  return typeof value === "string" ? value.trim() : "";
880083
880203
  }
880084
- function readLimit7(value, fallback) {
880204
+ function readLimit8(value, fallback) {
880085
880205
  const parsed = typeof value === "string" && value.trim() ? Number(value) : value;
880086
880206
  if (typeof parsed !== "number" || !Number.isFinite(parsed))
880087
880207
  return fallback;
@@ -880136,16 +880256,16 @@ function listHarnessPanels(context, args2) {
880136
880256
  const manager5 = panelManager(context);
880137
880257
  if (!manager5)
880138
880258
  return [];
880139
- const query2 = readString52(args2.query);
880140
- const category = readString52(args2.category);
880141
- const limit3 = readLimit7(args2.limit, 200);
880259
+ const query2 = readString53(args2.query);
880260
+ const category = readString53(args2.category);
880261
+ const limit3 = readLimit8(args2.limit, 200);
880142
880262
  return manager5.getRegisteredTypes().map((registration) => describePanelRegistration(context, registration)).filter((panel) => !category || panel.category === category).filter((panel) => panelMatches(panel, query2)).sort((a4, b3) => String(a4.id).localeCompare(String(b3.id))).slice(0, limit3);
880143
880263
  }
880144
880264
  function describeHarnessPanel(context, args2) {
880145
880265
  const manager5 = panelManager(context);
880146
880266
  if (!manager5)
880147
880267
  return null;
880148
- const panelId = readString52(args2.panelId || args2.query);
880268
+ const panelId = readString53(args2.panelId || args2.query);
880149
880269
  if (!panelId)
880150
880270
  return null;
880151
880271
  const registration = manager5.getRegisteredTypes().find((panel) => panel.id === panelId || panel.name.toLowerCase() === panelId.toLowerCase());
@@ -880156,11 +880276,11 @@ function openHarnessPanel(context, args2) {
880156
880276
  if (!panel) {
880157
880277
  return {
880158
880278
  status: "unknown_panel",
880159
- panelId: readString52(args2.panelId || args2.query) || "<missing>",
880279
+ panelId: readString53(args2.panelId || args2.query) || "<missing>",
880160
880280
  availablePanels: listHarnessPanels(context, { limit: 50 }).map((entry) => entry.id)
880161
880281
  };
880162
880282
  }
880163
- const requestedPane = readString52(args2.pane);
880283
+ const requestedPane = readString53(args2.pane);
880164
880284
  const pane = requestedPane === "bottom" || requestedPane === "top" ? requestedPane : undefined;
880165
880285
  if (!context.showPanel) {
880166
880286
  return {
@@ -880184,7 +880304,7 @@ import { join as join98 } from "path";
880184
880304
  function isRecord42(value) {
880185
880305
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
880186
880306
  }
880187
- function readString53(record2, key) {
880307
+ function readString54(record2, key) {
880188
880308
  const value = record2?.[key];
880189
880309
  return typeof value === "string" ? value : null;
880190
880310
  }
@@ -880235,7 +880355,7 @@ async function inspectCliExternalRuntime(options) {
880235
880355
  try {
880236
880356
  const status = await fetchJson2(`${baseUrl}/status`, token.token, timeoutMs);
880237
880357
  const statusRecord = isRecord42(status.body) ? status.body : {};
880238
- const version6 = readString53(statusRecord, "version") ?? "unknown";
880358
+ const version6 = readString54(statusRecord, "version") ?? "unknown";
880239
880359
  const compatible = status.ok && version6 === SDK_VERSION;
880240
880360
  if (!status.ok) {
880241
880361
  return {
@@ -880453,7 +880573,7 @@ function output6(value) {
880453
880573
  function error51(message) {
880454
880574
  return { success: false, error: message };
880455
880575
  }
880456
- function readString54(value) {
880576
+ function readString55(value) {
880457
880577
  return typeof value === "string" ? value.trim() : "";
880458
880578
  }
880459
880579
  function readFieldMap2(value) {
@@ -880480,10 +880600,10 @@ function describeEditor(editor) {
880480
880600
  }
880481
880601
  function readRecordId(args2) {
880482
880602
  const fields = readFieldMap2(args2.fields);
880483
- return readString54(args2.recordId ?? args2.id ?? fields.recordId ?? fields.id);
880603
+ return readString55(args2.recordId ?? args2.id ?? fields.recordId ?? fields.id);
880484
880604
  }
880485
880605
  function requireConfirmed(args2, label) {
880486
- if (!readString54(args2.explicitUserRequest))
880606
+ if (!readString55(args2.explicitUserRequest))
880487
880607
  return `${label} requires explicitUserRequest with the user's exact request or a short faithful summary.`;
880488
880608
  if (args2.confirm !== true)
880489
880609
  return `${label} requires confirm:true after an explicit user request.`;
@@ -880611,7 +880731,7 @@ function localRegistryArgsForTarget(target, args2, recordId) {
880611
880731
  ...base2,
880612
880732
  ...edit,
880613
880733
  ...target.action === "stale" && fields.reason ? { reason: fields.reason } : {},
880614
- ...target.action === "delete" ? { confirm: args2.confirm, explicitUserRequest: readString54(args2.explicitUserRequest) } : {}
880734
+ ...target.action === "delete" ? { confirm: args2.confirm, explicitUserRequest: readString55(args2.explicitUserRequest) } : {}
880615
880735
  };
880616
880736
  }
880617
880737
  async function executeTool(toolRegistry, name51, toolArgs) {
@@ -880677,7 +880797,7 @@ async function runLocalWorkspaceAction(deps, action2, args2) {
880677
880797
  if (missing.length > 0)
880678
880798
  return output6({ status: "missing_required_fields", missing, action: action2.id, editor: describeEditor(editor) });
880679
880799
  if (editor.kind === "knowledge-url")
880680
- return executeTool(deps.toolRegistry, "agent_knowledge_ingest", { sourceKind: "url", url: fieldReader(editor, fields)("url"), tags: splitList5(fieldReader(editor, fields)("tags")), folderPath: fieldReader(editor, fields)("folder"), confirm: args2.confirm, explicitUserRequest: readString54(args2.explicitUserRequest) });
880800
+ return executeTool(deps.toolRegistry, "agent_knowledge_ingest", { sourceKind: "url", url: fieldReader(editor, fields)("url"), tags: splitList5(fieldReader(editor, fields)("tags")), folderPath: fieldReader(editor, fields)("folder"), confirm: args2.confirm, explicitUserRequest: readString55(args2.explicitUserRequest) });
880681
880801
  return executeTool(deps.toolRegistry, "agent_local_registry", localRegistryArgsFromEditor(editor, fields, recordId));
880682
880802
  }
880683
880803
  if (!target)
@@ -880686,19 +880806,19 @@ async function runLocalWorkspaceAction(deps, action2, args2) {
880686
880806
  }
880687
880807
 
880688
880808
  // src/tools/agent-harness-model-tool-catalog.ts
880689
- function readString55(value) {
880809
+ function readString56(value) {
880690
880810
  return typeof value === "string" ? value.trim() : "";
880691
880811
  }
880692
- function readLimit8(value, fallback) {
880812
+ function readLimit9(value, fallback) {
880693
880813
  const parsed = typeof value === "string" && value.trim() ? Number(value) : value;
880694
880814
  if (typeof parsed !== "number" || !Number.isFinite(parsed))
880695
880815
  return fallback;
880696
880816
  return Math.max(1, Math.min(500, Math.trunc(parsed)));
880697
880817
  }
880698
880818
  function listHarnessModelTools(toolRegistry, args2) {
880699
- const query2 = readString55(args2.query).toLowerCase();
880819
+ const query2 = readString56(args2.query).toLowerCase();
880700
880820
  const includeParameters = args2.includeParameters === true;
880701
- const limit3 = readLimit8(args2.limit, 200);
880821
+ const limit3 = readLimit9(args2.limit, 200);
880702
880822
  return toolRegistry.getToolDefinitions().filter((tool2) => !query2 || [tool2.name, tool2.description, ...tool2.sideEffects ?? []].join(`
880703
880823
  `).toLowerCase().includes(query2)).sort((a4, b3) => a4.name.localeCompare(b3.name)).slice(0, limit3).map((tool2) => ({
880704
880824
  name: tool2.name,
@@ -880711,7 +880831,7 @@ function listHarnessModelTools(toolRegistry, args2) {
880711
880831
  }));
880712
880832
  }
880713
880833
  function describeHarnessModelTool(toolRegistry, args2) {
880714
- const query2 = readString55(args2.toolName || args2.target || args2.query).toLowerCase();
880834
+ const query2 = readString56(args2.toolName || args2.target || args2.query).toLowerCase();
880715
880835
  if (!query2)
880716
880836
  return null;
880717
880837
  const tool2 = toolRegistry.getToolDefinitions().find((candidate) => candidate.name.toLowerCase() === query2 || candidate.name.toLowerCase().includes(query2));
@@ -880777,11 +880897,11 @@ var AGENT_HARNESS_PARAMETER_PROPERTIES = {
880777
880897
  },
880778
880898
  query: {
880779
880899
  type: "string",
880780
- description: "Search text for command, setting, tool, or UI surface catalogs."
880900
+ description: "Search text for command, setting, tool, or UI surface catalogs, or a slash-command lookup for mode command."
880781
880901
  },
880782
880902
  command: {
880783
880903
  type: "string",
880784
- description: 'Full slash command string for mode run_command, for example "/settings get provider.model". In cli_command mode this may also hold a top-level CLI string such as "goodvibes-agent status --json".'
880904
+ description: 'Full slash command string for mode command or run_command, for example "/settings get provider.model". In cli_command mode this may also hold a top-level CLI string such as "goodvibes-agent status --json".'
880785
880905
  },
880786
880906
  cliCommand: {
880787
880907
  type: "string",
@@ -880844,7 +880964,7 @@ var AGENT_HARNESS_PARAMETER_PROPERTIES = {
880844
880964
  },
880845
880965
  target: {
880846
880966
  type: "string",
880847
- description: "Optional lookup target, such as a model-picker target, settings key, model tool name, or connected-host capability id."
880967
+ description: "Optional lookup target, such as a model-picker target, slash command root or invocation, settings key, model tool name, or connected-host capability id."
880848
880968
  },
880849
880969
  capabilityId: {
880850
880970
  type: "string",
@@ -880985,10 +881105,10 @@ async function openTtsVoicePicker(ctx, providerArg) {
880985
881105
  }
880986
881106
 
880987
881107
  // src/tools/agent-harness-ui-surface-metadata.ts
880988
- function readString56(value) {
881108
+ function readString57(value) {
880989
881109
  return typeof value === "string" ? value.trim() : "";
880990
881110
  }
880991
- function readLimit9(value, fallback) {
881111
+ function readLimit10(value, fallback) {
880992
881112
  const parsed = typeof value === "string" && value.trim() ? Number(value) : value;
880993
881113
  if (typeof parsed !== "number" || !Number.isFinite(parsed))
880994
881114
  return fallback;
@@ -881011,35 +881131,35 @@ function opened(surface, extra = {}) {
881011
881131
  };
881012
881132
  }
881013
881133
  function optionalModelTarget(args2) {
881014
- const target = readString56(args2.target);
881134
+ const target = readString57(args2.target);
881015
881135
  return target === "main" || target === "helper" || target === "tool" || target === "tts" ? target : undefined;
881016
881136
  }
881017
881137
  function optionalOnboardingMode(args2) {
881018
- const target = readString56(args2.target);
881138
+ const target = readString57(args2.target);
881019
881139
  return target === "new" || target === "edit" || target === "reopen" ? target : undefined;
881020
881140
  }
881021
881141
  function workspaceCategory(args2) {
881022
- return readString56(args2.categoryId || args2.category || args2.target) || undefined;
881142
+ return readString57(args2.categoryId || args2.category || args2.target) || undefined;
881023
881143
  }
881024
881144
  function settingsTarget(args2) {
881025
- return readString56(args2.target || args2.key || args2.prefix) || undefined;
881145
+ return readString57(args2.target || args2.key || args2.prefix) || undefined;
881026
881146
  }
881027
881147
  function providerTarget(args2) {
881028
- return readString56(args2.target || args2.key || args2.prefix) || undefined;
881148
+ return readString57(args2.target || args2.key || args2.prefix) || undefined;
881029
881149
  }
881030
881150
  function optionalPane(args2) {
881031
- const pane = readString56(args2.pane);
881151
+ const pane = readString57(args2.pane);
881032
881152
  return pane === "top" || pane === "bottom" ? pane : undefined;
881033
881153
  }
881034
881154
  function surfaceInputText(args2) {
881035
- return readString56(args2.prefix || args2.key || (args2.surfaceId ? args2.query : undefined) || args2.target) || undefined;
881155
+ return readString57(args2.prefix || args2.key || (args2.surfaceId ? args2.query : undefined) || args2.target) || undefined;
881036
881156
  }
881037
881157
  function filePickerOptions(args2) {
881038
- const target = readString56(args2.target).toLowerCase();
881158
+ const target = readString57(args2.target).toLowerCase();
881039
881159
  const injectMode = target === "inject" || target === "inject-mode" || target === "!@";
881040
881160
  return {
881041
881161
  injectMode,
881042
- query: readString56(args2.prefix || args2.key || (args2.surfaceId ? args2.query : undefined)) || undefined
881162
+ query: readString57(args2.prefix || args2.key || (args2.surfaceId ? args2.query : undefined)) || undefined
881043
881163
  };
881044
881164
  }
881045
881165
  function openAgentWorkspaceCategory(context, surface, categoryId, extra = {}) {
@@ -881615,19 +881735,19 @@ function totalHarnessUiSurfaces() {
881615
881735
  return UI_SURFACES.length;
881616
881736
  }
881617
881737
  function listHarnessUiSurfaces(context, args2) {
881618
- const query2 = readString56(args2.query);
881619
- const limit3 = readLimit9(args2.limit, 200);
881738
+ const query2 = readString57(args2.query);
881739
+ const limit3 = readLimit10(args2.limit, 200);
881620
881740
  return UI_SURFACES.map((surface) => describeSurface(context, surface)).filter((surface) => surfaceMatches(surface, query2)).slice(0, limit3);
881621
881741
  }
881622
881742
  function describeHarnessUiSurface(context, args2) {
881623
- const surfaceId = readString56(args2.surfaceId || args2.query);
881743
+ const surfaceId = readString57(args2.surfaceId || args2.query);
881624
881744
  if (!surfaceId)
881625
881745
  return null;
881626
881746
  const surface = UI_SURFACES.find((entry) => entry.id === surfaceId || entry.label.toLowerCase() === surfaceId.toLowerCase());
881627
881747
  return surface ? describeSurface(context, surface) : null;
881628
881748
  }
881629
881749
  async function openHarnessUiSurface(context, args2) {
881630
- const surfaceId = readString56(args2.surfaceId || args2.query);
881750
+ const surfaceId = readString57(args2.surfaceId || args2.query);
881631
881751
  const surface = UI_SURFACES.find((entry) => entry.id === surfaceId || entry.label.toLowerCase() === surfaceId.toLowerCase());
881632
881752
  if (!surface) {
881633
881753
  return {
@@ -881647,16 +881767,16 @@ async function openHarnessUiSurface(context, args2) {
881647
881767
  function isMode(value) {
881648
881768
  return typeof value === "string" && AGENT_HARNESS_MODES.includes(value);
881649
881769
  }
881650
- function readString57(value) {
881770
+ function readString58(value) {
881651
881771
  return typeof value === "string" ? value.trim() : "";
881652
881772
  }
881653
- function readLimit10(value, fallback) {
881773
+ function readLimit11(value, fallback) {
881654
881774
  const parsed = typeof value === "string" && value.trim() ? Number(value) : value;
881655
881775
  if (typeof parsed !== "number" || !Number.isFinite(parsed))
881656
881776
  return fallback;
881657
881777
  return Math.max(1, Math.min(500, Math.trunc(parsed)));
881658
881778
  }
881659
- function readStringArray12(value) {
881779
+ function readStringArray13(value) {
881660
881780
  if (!Array.isArray(value))
881661
881781
  return [];
881662
881782
  return value.map((entry) => typeof entry === "string" ? entry : String(entry));
@@ -881675,30 +881795,6 @@ function output7(value) {
881675
881795
  function error52(message) {
881676
881796
  return { success: false, error: message };
881677
881797
  }
881678
- function commandMatches(command8, query2) {
881679
- if (!query2)
881680
- return true;
881681
- const haystack = [
881682
- command8.name,
881683
- ...command8.aliases ?? [],
881684
- command8.description,
881685
- command8.usage ?? "",
881686
- command8.argsHint ?? ""
881687
- ].join(`
881688
- `).toLowerCase();
881689
- return haystack.includes(query2.toLowerCase());
881690
- }
881691
- function describeCommand(command8) {
881692
- return {
881693
- name: command8.name,
881694
- slash: `/${command8.name}`,
881695
- aliases: command8.aliases ?? [],
881696
- description: command8.description,
881697
- usage: command8.usage ?? "",
881698
- argsHint: command8.argsHint ?? command8.usage ?? "",
881699
- policy: describeCommandPolicy(command8.name)
881700
- };
881701
- }
881702
881798
  function allWorkspaceActions() {
881703
881799
  return AGENT_WORKSPACE_CATEGORIES.flatMap((category) => category.actions.map((action2) => ({ category, action: action2 })));
881704
881800
  }
@@ -881731,7 +881827,7 @@ function describeWorkspaceEditor(editor) {
881731
881827
  }
881732
881828
  function selectedRoutineFromArgs(snapshot, args2) {
881733
881829
  const fields = readFieldMap3(args2.fields);
881734
- const routineId = readString57(args2.recordId) || readString57(fields.routineId) || readString57(fields.id);
881830
+ const routineId = readString58(args2.recordId) || readString58(fields.routineId) || readString58(fields.id);
881735
881831
  if (!routineId)
881736
881832
  return null;
881737
881833
  return snapshot.localRoutines.find((routine) => routine.id === routineId || routine.name.toLowerCase() === routineId.toLowerCase()) ?? null;
@@ -881799,17 +881895,17 @@ function localEditorModelExecution(editorKind) {
881799
881895
  return "Use the command field, editor schema, or a first-class Agent model tool when available.";
881800
881896
  }
881801
881897
  function listWorkspaceActions(deps, args2) {
881802
- const query2 = readString57(args2.query);
881803
- const categoryId = readString57(args2.categoryId || args2.category);
881804
- const limit3 = readLimit10(args2.limit, 200);
881898
+ const query2 = readString58(args2.query);
881899
+ const categoryId = readString58(args2.categoryId || args2.category);
881900
+ const limit3 = readLimit11(args2.limit, 200);
881805
881901
  const includeEditor = args2.includeParameters === true;
881806
881902
  const editorContext = includeEditor ? buildWorkspaceEditorContext(deps.commandContext, args2) : null;
881807
881903
  const source = query2 ? searchAgentWorkspaceActions(AGENT_WORKSPACE_CATEGORIES, query2).map((result2) => ({ category: result2.category, action: result2.action })) : allWorkspaceActions();
881808
881904
  return source.filter((entry) => !categoryId || entry.category.id === categoryId).slice(0, limit3).map((entry) => describeWorkspaceAction(entry.category, entry.action, { includeEditor, editorContext }));
881809
881905
  }
881810
881906
  function findWorkspaceAction(args2) {
881811
- const actionId = readString57(args2.actionId || args2.query);
881812
- const categoryId = readString57(args2.categoryId || args2.category);
881907
+ const actionId = readString58(args2.actionId || args2.query);
881908
+ const categoryId = readString58(args2.categoryId || args2.category);
881813
881909
  if (!actionId)
881814
881910
  return null;
881815
881911
  return allWorkspaceActions().find((entry) => {
@@ -881818,13 +881914,8 @@ function findWorkspaceAction(args2) {
881818
881914
  return entry.action.id === actionId || entry.action.label.toLowerCase() === actionId.toLowerCase();
881819
881915
  }) ?? null;
881820
881916
  }
881821
- function listCommands(commandRegistry, args2) {
881822
- const query2 = readString57(args2.query);
881823
- const limit3 = readLimit10(args2.limit, 200);
881824
- return commandRegistry.list().filter((command8) => commandMatches(command8, query2)).sort((a4, b3) => a4.name.localeCompare(b3.name)).slice(0, limit3).map(describeCommand);
881825
- }
881826
881917
  function requireConfirmedAction(args2, action2) {
881827
- const explicitUserRequest = readString57(args2.explicitUserRequest);
881918
+ const explicitUserRequest = readString58(args2.explicitUserRequest);
881828
881919
  if (!explicitUserRequest)
881829
881920
  return `${action2} requires explicitUserRequest with the user's exact request or a short faithful summary.`;
881830
881921
  if (args2.confirm !== true)
@@ -881832,7 +881923,7 @@ function requireConfirmedAction(args2, action2) {
881832
881923
  return null;
881833
881924
  }
881834
881925
  function commandFromArgs(args2) {
881835
- const rawCommand = readString57(args2.command);
881926
+ const rawCommand = readString58(args2.command);
881836
881927
  if (rawCommand) {
881837
881928
  const parsed = parseSlashCommand(rawCommand);
881838
881929
  if (!parsed.name)
@@ -881842,10 +881933,10 @@ function commandFromArgs(args2) {
881842
881933
  args: parsed.args
881843
881934
  };
881844
881935
  }
881845
- const commandName = readString57(args2.commandName).replace(/^\//, "");
881936
+ const commandName = readString58(args2.commandName).replace(/^\//, "");
881846
881937
  if (!commandName)
881847
881938
  return null;
881848
- const commandArgs = readStringArray12(args2.args);
881939
+ const commandArgs = readStringArray13(args2.args);
881849
881940
  return {
881850
881941
  name: commandName,
881851
881942
  args: commandArgs
@@ -882069,7 +882160,7 @@ function createAgentHarnessTool(deps) {
882069
882160
  panels: 'Use mode:"panels" and mode:"panel" to inspect built-in panel catalog/open state; use mode:"open_panel" with confirm:true plus explicitUserRequest to route a visible panel/workspace change.',
882070
882161
  uiSurfaces: 'Use mode:"ui_surfaces" and mode:"ui_surface" to inspect modal/overlay/picker/workspace surfaces; use mode:"open_ui_surface" with confirm:true plus explicitUserRequest to route visible UI navigation.',
882071
882162
  shortcuts: 'Use mode:"shortcuts" to inspect fixed shortcuts plus configurable keybindings. Use mode:"set_keybinding" and mode:"reset_keybinding" with confirm:true plus explicitUserRequest to edit the same config file the user edits.',
882072
- slashCommands: 'Use mode:"commands" and mode:"command" to inspect; use mode:"run_command" with confirm:true plus explicitUserRequest to execute.',
882163
+ slashCommands: 'Use mode:"commands" to list slash commands and mode:"command" with command, commandName, target, or query to inspect one command; use mode:"run_command" with confirm:true plus explicitUserRequest to execute.',
882073
882164
  workspace: 'Use mode:"workspace_actions" to list and mode:"workspace_action" for editor schemas; set includeParameters:true on workspace_actions to inline editor schemas.',
882074
882165
  settings: 'Use mode:"settings", mode:"get_setting", mode:"set_setting", and mode:"reset_setting".',
882075
882166
  tools: 'Use mode:"tools" to list first-class model tools, or mode:"tool" with toolName, target, or query to inspect one schema.',
@@ -882104,7 +882195,7 @@ function createAgentHarnessTool(deps) {
882104
882195
  }
882105
882196
  if (args2.mode === "panel") {
882106
882197
  const panel = describeHarnessPanel(deps.commandContext, args2);
882107
- return panel ? output7(panel) : error52(`Unknown panel ${readString57(args2.panelId || args2.query) || "<missing>"}.`);
882198
+ return panel ? output7(panel) : error52(`Unknown panel ${readString58(args2.panelId || args2.query) || "<missing>"}.`);
882108
882199
  }
882109
882200
  if (args2.mode === "open_panel") {
882110
882201
  const confirmationError2 = requireConfirmedAction(args2, "Panel routing");
@@ -882118,7 +882209,7 @@ function createAgentHarnessTool(deps) {
882118
882209
  }
882119
882210
  if (args2.mode === "ui_surface") {
882120
882211
  const surface = describeHarnessUiSurface(deps.commandContext, args2);
882121
- return surface ? output7(surface) : error52(`Unknown UI surface ${readString57(args2.surfaceId || args2.query) || "<missing>"}.`);
882212
+ return surface ? output7(surface) : error52(`Unknown UI surface ${readString58(args2.surfaceId || args2.query) || "<missing>"}.`);
882122
882213
  }
882123
882214
  if (args2.mode === "open_ui_surface") {
882124
882215
  const confirmationError2 = requireConfirmedAction(args2, "UI surface routing");
@@ -882132,7 +882223,7 @@ function createAgentHarnessTool(deps) {
882132
882223
  return output7(listHarnessKeybindings(deps.commandContext, args2));
882133
882224
  if (args2.mode === "keybinding") {
882134
882225
  const binding = describeHarnessKeybinding(deps.commandContext, args2);
882135
- return binding ? output7(binding) : error52(`Unknown keybinding action ${readString57(args2.actionId || args2.key || args2.query) || "<missing>"}.`);
882226
+ return binding ? output7(binding) : error52(`Unknown keybinding action ${readString58(args2.actionId || args2.key || args2.query) || "<missing>"}.`);
882136
882227
  }
882137
882228
  if (args2.mode === "set_keybinding") {
882138
882229
  const confirmationError2 = requireConfirmedAction(args2, "Keybinding mutation");
@@ -882147,28 +882238,28 @@ function createAgentHarnessTool(deps) {
882147
882238
  return output7(resetHarnessKeybinding(deps.commandContext, args2));
882148
882239
  }
882149
882240
  if (args2.mode === "commands") {
882150
- const commands3 = listCommands(deps.commandRegistry, args2);
882241
+ const commands3 = listHarnessCommands(deps.commandRegistry, args2);
882151
882242
  return output7({ commands: commands3, returned: commands3.length, total: deps.commandRegistry.list().length });
882152
882243
  }
882153
882244
  if (args2.mode === "command") {
882154
- const name51 = readString57(args2.commandName).replace(/^\//, "");
882155
- const command8 = name51 ? deps.commandRegistry.get(name51) : null;
882156
- return command8 ? output7(describeCommand(command8)) : error52(`Unknown slash command /${name51 || "<missing>"}.`);
882245
+ const detail = describeHarnessCommand(deps.commandRegistry, args2);
882246
+ const query2 = readString58(args2.command || args2.commandName || args2.target || args2.query);
882247
+ return detail ? output7(detail) : error52(`Unknown slash command ${query2 || "<missing>"}. Use mode:"commands" to inspect available commands.`);
882157
882248
  }
882158
882249
  if (args2.mode === "run_command")
882159
882250
  return runCommand2(deps, args2);
882160
882251
  if (args2.mode === "settings") {
882161
882252
  const settings = listHarnessSettings(deps.commandContext.platform.configManager, {
882162
- category: readString57(args2.category) || undefined,
882163
- prefix: readString57(args2.prefix) || undefined,
882164
- query: readString57(args2.query) || undefined,
882253
+ category: readString58(args2.category) || undefined,
882254
+ prefix: readString58(args2.prefix) || undefined,
882255
+ query: readString58(args2.query) || undefined,
882165
882256
  includeHidden: args2.includeHidden === true,
882166
- limit: readLimit10(args2.limit, 100)
882257
+ limit: readLimit11(args2.limit, 100)
882167
882258
  });
882168
882259
  return output7({ settings, returned: settings.length, policy: settingsPolicySummary() });
882169
882260
  }
882170
882261
  if (args2.mode === "get_setting") {
882171
- const key = readString57(args2.key);
882262
+ const key = readString58(args2.key);
882172
882263
  const setting = getHarnessSetting(deps.commandContext.platform.configManager, key);
882173
882264
  return setting ? output7(setting) : error52(`Unknown setting ${key || "<missing>"}.`);
882174
882265
  }
@@ -882178,7 +882269,7 @@ function createAgentHarnessTool(deps) {
882178
882269
  return error52(confirmationError2);
882179
882270
  if (args2.value === undefined)
882180
882271
  return error52("set_setting requires value.");
882181
- const key = readString57(args2.key);
882272
+ const key = readString58(args2.key);
882182
882273
  const result2 = await setHarnessSetting(deps.commandContext.platform.configManager, deps.commandContext.platform.secretsManager, key, args2.value);
882183
882274
  return output7(result2);
882184
882275
  }
@@ -882186,7 +882277,7 @@ function createAgentHarnessTool(deps) {
882186
882277
  const confirmationError2 = requireConfirmedAction(args2, "Setting reset");
882187
882278
  if (confirmationError2)
882188
882279
  return error52(confirmationError2);
882189
- const key = readString57(args2.key);
882280
+ const key = readString58(args2.key);
882190
882281
  const result2 = await resetHarnessSetting(deps.commandContext.platform.configManager, deps.commandContext.platform.secretsManager, key);
882191
882282
  return output7(result2);
882192
882283
  }
@@ -882203,7 +882294,7 @@ function createAgentHarnessTool(deps) {
882203
882294
  if (args2.mode === "workspace_action") {
882204
882295
  const found = findWorkspaceAction(args2);
882205
882296
  const editorContext = buildWorkspaceEditorContext(deps.commandContext, args2);
882206
- return found ? output7(describeWorkspaceAction(found.category, found.action, { includeEditor: true, editorContext })) : error52(`Unknown Agent workspace action ${readString57(args2.actionId || args2.query) || "<missing>"}.`);
882297
+ return found ? output7(describeWorkspaceAction(found.category, found.action, { includeEditor: true, editorContext })) : error52(`Unknown Agent workspace action ${readString58(args2.actionId || args2.query) || "<missing>"}.`);
882207
882298
  }
882208
882299
  if (args2.mode === "run_workspace_action")
882209
882300
  return runWorkspaceAction(deps, args2);
@@ -882212,14 +882303,14 @@ function createAgentHarnessTool(deps) {
882212
882303
  return output7({ tools: tools3, returned: tools3.length, total: deps.toolRegistry.getToolDefinitions().length });
882213
882304
  }
882214
882305
  if (args2.mode === "tool") {
882215
- const query2 = readString57(args2.toolName || args2.target || args2.query);
882306
+ const query2 = readString58(args2.toolName || args2.target || args2.query);
882216
882307
  const tool2 = describeHarnessModelTool(deps.toolRegistry, args2);
882217
882308
  return tool2 ? output7(tool2) : error52(`Unknown model tool ${query2 || "<missing>"}. Use mode:"tools" to inspect available model tools.`);
882218
882309
  }
882219
882310
  if (args2.mode === "connected_host")
882220
882311
  return output7(connectedHostSummary(deps.commandContext, deps.toolRegistry));
882221
882312
  if (args2.mode === "connected_host_capability") {
882222
- const query2 = readString57(args2.capabilityId || args2.target || args2.query);
882313
+ const query2 = readString58(args2.capabilityId || args2.target || args2.query);
882223
882314
  const capability = describeConnectedHostCapability(deps.toolRegistry, query2);
882224
882315
  return capability ? output7(capability) : error52(`Unknown connected-host capability ${query2 || "<missing>"}. Use mode:"connected_host" to inspect allowed and blocked capability ids.`);
882225
882316
  }
package/docs/README.md CHANGED
@@ -22,7 +22,7 @@ Important baseline constraints:
22
22
  - Agent depends on `@pellux/goodvibes-sdk@0.33.35`.
23
23
  - Agent connects to a GoodVibes host owned outside this product.
24
24
  - Agent does not start, stop, restart, install, uninstall, or own the connected GoodVibes host.
25
- - User-facing Agent workspace actions, built-in panels, modal/overlay/picker UI surfaces, named operator surfaces, top-level CLI mirrors, fixed shortcuts, configurable keybindings, slash commands, command policy metadata, model tools and individual model tool schemas, settings, local registries, connected-host capability boundaries, single connected-host capability inspection, and live connected-host readiness are model-visible through Agent-owned tools. Visible UI routing includes the command browser, reasoning-effort picker, live process output, runtime activity, settings, workspaces, and pickers the user can open. Local memory, note, persona, skill, and routine create editors can also run through confirmed workspace actions. Mutations and visible UI routing remain explicit and confirmation-gated.
25
+ - User-facing Agent workspace actions, built-in panels, modal/overlay/picker UI surfaces, named operator surfaces, top-level CLI mirrors, fixed shortcuts, configurable keybindings, slash commands, single-command lookup with policy metadata, model tools and individual model tool schemas, settings, local registries, connected-host capability boundaries, single connected-host capability inspection, and live connected-host readiness are model-visible through Agent-owned tools. Visible UI routing includes the command browser, reasoning-effort picker, live process output, runtime activity, settings, workspaces, and pickers the user can open. Local memory, note, persona, skill, and routine create editors can also run through confirmed workspace actions. Mutations and visible UI routing remain explicit and confirmation-gated.
26
26
  - Agent Knowledge uses only `/api/goodvibes-agent/knowledge/*`; there is no default knowledge or non-Agent product fallback.
27
27
  - Agent supports isolated Agent homes with `GOODVIBES_AGENT_HOME=<path>` and named profile homes with `goodvibes-agent profiles create <name> --template <starter> --yes` plus `--agent-profile <name>`.
28
28
  - Agent supports connected-host URL overrides with `--runtime-url http://host:port` or `GOODVIBES_AGENT_RUNTIME_URL=http://host:port`; these only change the Agent connection target.
@@ -44,7 +44,7 @@ After setup has been applied once, the TUI opens directly into the Agent operato
44
44
 
45
45
  Press `/` inside the Agent workspace to search every workspace action by name, category, command, or detail. Use that finder before reaching for shell commands; CLI subcommands are scriptable mirrors of these TUI workflows.
46
46
 
47
- The model can inspect and use the same harness surface through Agent-owned tools. `agent_harness` exposes workspace action discovery, built-in panel discovery/routing, modal/overlay/picker UI surface discovery/routing, top-level CLI mirror discovery, fixed shortcut and configurable keybinding discovery, keybinding mutation with confirmation, slash-command discovery, command policy metadata, command execution with confirmation, model tool discovery and single-tool schema inspection, settings inspection/mutation, connected-host capability inventory, single connected-host capability inspection, and live connected-host readiness posture. CLI mirror modes are read-only catalog/parser inspection and point the model to the matching in-process tool, workspace action, setting mode, or slash-command mirror. Panel modes expose catalog/open state and route visible panel/workspace changes through the current Agent shell bridge with confirmation. UI surface modes expose help, shortcuts, command browser, conversation search, prompt-history search, slash-command mode, file picker, block actions, context, runtime activity, live process output, bookmarks, model/provider/reasoning-effort pickers, TTS provider/voice pickers, session/profile pickers, the panel-picker compatibility route, security/knowledge/subscription operator surfaces, settings, MCP workspace, onboarding, and Agent workspace entrypoints; `open_ui_surface` is confirmation-gated and only performs visible shell navigation. Shortcut modes expose the fixed runtime/editor shortcuts and the live keybindings table; confirmed keybinding edits write the same `keybindings.json` file the user edits and reload the runtime manager. Workspace action discovery can include editor field schemas with `includeParameters:true`, including starter-template defaults for profile creation and selected-routine defaults for routine schedule promotion when `recordId` is supplied. Selection-based local workspace actions accept a local `recordId`, so the model can use the same note promotion and local registry flows as the TUI. Direct local create editors for memory, notes, personas, skills, and routines can execute from submitted fields through `run_workspace_action` and `agent_local_registry` with confirmation. First-class model tools cover the main product workflows directly: Agent Knowledge, Agent Knowledge ingest, Agent-local memory/notes/personas/skills/routines, operator actions, notifications, channel sends, reminders, generated media, and work plans.
47
+ The model can inspect and use the same harness surface through Agent-owned tools. `agent_harness` exposes workspace action discovery, built-in panel discovery/routing, modal/overlay/picker UI surface discovery/routing, top-level CLI mirror discovery, fixed shortcut and configurable keybinding discovery, keybinding mutation with confirmation, slash-command discovery, single slash-command inspection by typed command or lookup text, command policy metadata, command execution with confirmation, model tool discovery and single-tool schema inspection, settings inspection/mutation, connected-host capability inventory, single connected-host capability inspection, and live connected-host readiness posture. CLI mirror modes are read-only catalog/parser inspection and point the model to the matching in-process tool, workspace action, setting mode, or slash-command mirror. Panel modes expose catalog/open state and route visible panel/workspace changes through the current Agent shell bridge with confirmation. UI surface modes expose help, shortcuts, command browser, conversation search, prompt-history search, slash-command mode, file picker, block actions, context, runtime activity, live process output, bookmarks, model/provider/reasoning-effort pickers, TTS provider/voice pickers, session/profile pickers, the panel-picker compatibility route, security/knowledge/subscription operator surfaces, settings, MCP workspace, onboarding, and Agent workspace entrypoints; `open_ui_surface` is confirmation-gated and only performs visible shell navigation. Shortcut modes expose the fixed runtime/editor shortcuts and the live keybindings table; confirmed keybinding edits write the same `keybindings.json` file the user edits and reload the runtime manager. Workspace action discovery can include editor field schemas with `includeParameters:true`, including starter-template defaults for profile creation and selected-routine defaults for routine schedule promotion when `recordId` is supplied. Selection-based local workspace actions accept a local `recordId`, so the model can use the same note promotion and local registry flows as the TUI. Direct local create editors for memory, notes, personas, skills, and routines can execute from submitted fields through `run_workspace_action` and `agent_local_registry` with confirmation. First-class model tools cover the main product workflows directly: Agent Knowledge, Agent Knowledge ingest, Agent-local memory/notes/personas/skills/routines, operator actions, notifications, channel sends, reminders, generated media, and work plans.
48
48
 
49
49
  Use the Artifacts area for concrete files and generated output: attach images to prompts, export conversations or saved sessions, ingest local source files and URL lists into Agent Knowledge, import bookmarks or browser history, inspect source records, and generate media artifacts from confirmed prompts.
50
50
 
@@ -43,14 +43,14 @@ Host-management and coding-first commands that would imply connected-host lifecy
43
43
 
44
44
  The main Agent model has an Agent-owned harness bridge rather than generic SDK settings/context control. Use these model tools as the supported parity layer:
45
45
 
46
- - `agent_harness`: inspect workspace categories/actions, inspect built-in panels, inspect modal/overlay/picker UI surfaces and named operator surfaces, inspect top-level CLI mirrors, inspect fixed shortcuts and configurable keybindings, inspect slash commands and command policy metadata, run concrete workspace or slash-command mirrors with confirmation, inspect model tools or one model tool schema, inspect or change Agent settings/keybindings, inspect connected-host capability boundaries or one connected-host capability, and inspect live connected-host readiness.
46
+ - `agent_harness`: inspect workspace categories/actions, inspect built-in panels, inspect modal/overlay/picker UI surfaces and named operator surfaces, inspect top-level CLI mirrors, inspect fixed shortcuts and configurable keybindings, inspect slash commands and one slash command by typed command or lookup text with policy metadata, run concrete workspace or slash-command mirrors with confirmation, inspect model tools or one model tool schema, inspect or change Agent settings/keybindings, inspect connected-host capability boundaries or one connected-host capability, and inspect live connected-host readiness.
47
47
  - `agent_local_registry`: inspect and maintain Agent-local memory, notes, personas, skills, skill bundles, and routines. Deleting local records requires `confirm:true` and `explicitUserRequest`.
48
48
  - `agent_knowledge` and `agent_knowledge_ingest`: ask/search and ingest into the isolated Agent Knowledge segment.
49
49
  - `agent_operator_briefing` and `agent_operator_action`: inspect connected work/approval/automation posture, or run exact confirmed approval/automation actions.
50
50
  - `agent_work_plan`: keep the visible Agent-local work plan current from the conversation.
51
51
  - `agent_channel_send`, `agent_notify`, `agent_reminder_schedule`, and `agent_media_generate`: perform confirmed external delivery, notification, reminder, or media actions when the user explicitly asks.
52
52
 
53
- `agent_harness` discovery modes are read-only. `summary` reports the model access map; `panels` and `panel` return the built-in panel catalog, current open/focused state, and each panel's matching Agent workspace route; `ui_surfaces` and `ui_surface` return modal, overlay, picker, and workspace entrypoints with shell-opener availability and preferred model routes; `cli_commands` and `cli_command` return top-level package CLI mirror metadata, parser output, blocked command tokens, and preferred in-process model routes; `shortcuts` returns fixed runtime/editor shortcuts plus configurable keybindings; `keybindings` and `keybinding` return the live resolved keybinding table, default bindings, custom state, and config path; `commands` and `command` return slash-command descriptions plus effect/confirmation/preferred-tool/boundary policy metadata; `workspace_actions` lists Agent workspace actions and can inline editor field schemas with `includeParameters:true`; `workspace_action` returns one action with its editor schema; `tools` lists model tool definitions and can inline JSON schemas with `includeParameters:true`; `tool` returns one model tool schema by `toolName`, `target`, or `query`; `settings` returns setting descriptors plus setting policy; `connected_host` returns the connected-host route families, allowed capabilities, blocked capabilities, and first-class tool availability; `connected_host_capability` returns one allowed or blocked connected-host capability by `capabilityId`, `target`, or `query` with related route families and boundary text; and `connected_host_status` performs a live read-only check of the connected-host status and Agent Knowledge status routes and reports endpoint bindings, token posture, SDK compatibility, route readiness, and findings without printing token values.
53
+ `agent_harness` discovery modes are read-only. `summary` reports the model access map; `panels` and `panel` return the built-in panel catalog, current open/focused state, and each panel's matching Agent workspace route; `ui_surfaces` and `ui_surface` return modal, overlay, picker, and workspace entrypoints with shell-opener availability and preferred model routes; `cli_commands` and `cli_command` return top-level package CLI mirror metadata, parser output, blocked command tokens, and preferred in-process model routes; `shortcuts` returns fixed runtime/editor shortcuts plus configurable keybindings; `keybindings` and `keybinding` return the live resolved keybinding table, default bindings, custom state, and config path; `commands` lists slash-command descriptions; `command` returns one slash-command detail by `command`, `commandName`, `target`, or `query`, including parsed arguments plus effect/confirmation/preferred-tool/boundary policy metadata; `workspace_actions` lists Agent workspace actions and can inline editor field schemas with `includeParameters:true`; `workspace_action` returns one action with its editor schema; `tools` lists model tool definitions and can inline JSON schemas with `includeParameters:true`; `tool` returns one model tool schema by `toolName`, `target`, or `query`; `settings` returns setting descriptors plus setting policy; `connected_host` returns the connected-host route families, allowed capabilities, blocked capabilities, and first-class tool availability; `connected_host_capability` returns one allowed or blocked connected-host capability by `capabilityId`, `target`, or `query` with related route families and boundary text; and `connected_host_status` performs a live read-only check of the connected-host status and Agent Knowledge status routes and reports endpoint bindings, token posture, SDK compatibility, route readiness, and findings without printing token values.
54
54
 
55
55
  `open_ui_surface` is a confirmation-gated visible navigation mode for the same shell surfaces the user can open: Agent workspace, settings, MCP workspace, model/provider/reasoning-effort pickers, TTS provider/voice pickers, session/profile pickers, the panel-picker compatibility route, security/knowledge/subscription operator surfaces, conversation search, prompt-history search, slash-command mode, command browser, file picker, block actions, bookmarks, context inspector, runtime activity monitor, live process output, help, shortcuts, and onboarding. It does not perform hidden operations; use first-class model tools, settings modes, workspace actions, or confirmed slash-command mirrors for actual state changes.
56
56
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pellux/goodvibes-agent",
3
- "version": "1.0.14",
3
+ "version": "1.0.15",
4
4
  "private": false,
5
5
  "description": "GoodVibes personal operator assistant TUI with a proactive Agent product brain, isolated Agent Knowledge, local profiles, routines, skills, personas, and explicit build delegation.",
6
6
  "type": "module",
@@ -0,0 +1,149 @@
1
+ import type { CommandRegistry, SlashCommand } from '../input/command-registry.ts';
2
+ import { parseSlashCommand } from '../input/slash-command-parser.ts';
3
+ import { describeCommandPolicy } from './agent-harness-metadata.ts';
4
+
5
+ export interface AgentHarnessCommandCatalogArgs {
6
+ readonly query?: unknown;
7
+ readonly command?: unknown;
8
+ readonly commandName?: unknown;
9
+ readonly args?: unknown;
10
+ readonly target?: unknown;
11
+ readonly limit?: unknown;
12
+ }
13
+
14
+ interface CommandDetailLookup {
15
+ readonly source: 'command' | 'commandName' | 'target' | 'query';
16
+ readonly input: string;
17
+ readonly parsedName: string;
18
+ readonly parsedArgs: readonly string[];
19
+ readonly resolvedBy: 'name' | 'alias' | 'case-insensitive-name' | 'case-insensitive-alias' | 'description' | 'prefix';
20
+ }
21
+
22
+ function readString(value: unknown): string {
23
+ return typeof value === 'string' ? value.trim() : '';
24
+ }
25
+
26
+ function readLimit(value: unknown, fallback: number): number {
27
+ const parsed = typeof value === 'string' && value.trim() ? Number(value) : value;
28
+ if (typeof parsed !== 'number' || !Number.isFinite(parsed)) return fallback;
29
+ return Math.max(1, Math.min(500, Math.trunc(parsed)));
30
+ }
31
+
32
+ function readStringArray(value: unknown): readonly string[] {
33
+ if (!Array.isArray(value)) return [];
34
+ return value.map((entry) => typeof entry === 'string' ? entry : String(entry));
35
+ }
36
+
37
+ function commandMatches(command: SlashCommand, query: string): boolean {
38
+ if (!query) return true;
39
+ const haystack = [
40
+ command.name,
41
+ ...(command.aliases ?? []),
42
+ command.description,
43
+ command.usage ?? '',
44
+ command.argsHint ?? '',
45
+ ].join('\n').toLowerCase();
46
+ return haystack.includes(query.toLowerCase());
47
+ }
48
+
49
+ function describeCommand(command: SlashCommand, lookup?: CommandDetailLookup): Record<string, unknown> {
50
+ return {
51
+ name: command.name,
52
+ slash: `/${command.name}`,
53
+ aliases: command.aliases ?? [],
54
+ description: command.description,
55
+ usage: command.usage ?? '',
56
+ argsHint: command.argsHint ?? command.usage ?? '',
57
+ ...(lookup ? {
58
+ lookup: {
59
+ source: lookup.source,
60
+ input: lookup.input,
61
+ parsedName: lookup.parsedName,
62
+ parsedArgs: lookup.parsedArgs,
63
+ resolvedBy: lookup.resolvedBy,
64
+ },
65
+ } : {}),
66
+ policy: describeCommandPolicy(command.name),
67
+ };
68
+ }
69
+
70
+ function commandDetailLookupFromArgs(args: AgentHarnessCommandCatalogArgs): Omit<CommandDetailLookup, 'resolvedBy'> | null {
71
+ const rawCommand = readString(args.command);
72
+ if (rawCommand) {
73
+ const parsed = parseSlashCommand(rawCommand);
74
+ return { source: 'command', input: rawCommand, parsedName: parsed.name, parsedArgs: parsed.args };
75
+ }
76
+ const rawCommandName = readString(args.commandName);
77
+ if (rawCommandName) {
78
+ const parsed = parseSlashCommand(rawCommandName);
79
+ const explicitArgs = readStringArray(args.args);
80
+ return {
81
+ source: 'commandName',
82
+ input: rawCommandName,
83
+ parsedName: parsed.name,
84
+ parsedArgs: explicitArgs.length > 0 ? explicitArgs : parsed.args,
85
+ };
86
+ }
87
+ const rawTarget = readString(args.target);
88
+ if (rawTarget) {
89
+ const parsed = parseSlashCommand(rawTarget);
90
+ return { source: 'target', input: rawTarget, parsedName: parsed.name, parsedArgs: parsed.args };
91
+ }
92
+ const rawQuery = readString(args.query);
93
+ if (rawQuery) {
94
+ const parsed = parseSlashCommand(rawQuery);
95
+ return { source: 'query', input: rawQuery, parsedName: parsed.name, parsedArgs: parsed.args };
96
+ }
97
+ return null;
98
+ }
99
+
100
+ function resolveCommandDetail(commandRegistry: CommandRegistry, args: AgentHarnessCommandCatalogArgs): { readonly command: SlashCommand; readonly lookup: CommandDetailLookup } | null {
101
+ const lookup = commandDetailLookupFromArgs(args);
102
+ if (!lookup?.parsedName) return null;
103
+ const direct = commandRegistry.get(lookup.parsedName);
104
+ if (direct) {
105
+ return {
106
+ command: direct,
107
+ lookup: {
108
+ ...lookup,
109
+ resolvedBy: direct.name === lookup.parsedName ? 'name' : 'alias',
110
+ },
111
+ };
112
+ }
113
+
114
+ const normalized = lookup.parsedName.toLowerCase();
115
+ for (const command of commandRegistry.list()) {
116
+ if (command.name.toLowerCase() === normalized) {
117
+ return { command, lookup: { ...lookup, resolvedBy: 'case-insensitive-name' } };
118
+ }
119
+ if ((command.aliases ?? []).some((alias) => alias.toLowerCase() === normalized)) {
120
+ return { command, lookup: { ...lookup, resolvedBy: 'case-insensitive-alias' } };
121
+ }
122
+ }
123
+
124
+ const descriptionMatches = commandRegistry.list().filter((command) => commandMatches(command, lookup.input));
125
+ if (descriptionMatches.length === 1) {
126
+ return { command: descriptionMatches[0]!, lookup: { ...lookup, resolvedBy: 'description' } };
127
+ }
128
+
129
+ const prefixMatches = commandRegistry.fuzzyMatch(lookup.parsedName).filter((entry) => entry.score >= 80);
130
+ if (prefixMatches.length === 1) {
131
+ return { command: prefixMatches[0]!.command, lookup: { ...lookup, resolvedBy: 'prefix' } };
132
+ }
133
+ return null;
134
+ }
135
+
136
+ export function listHarnessCommands(commandRegistry: CommandRegistry, args: AgentHarnessCommandCatalogArgs): readonly Record<string, unknown>[] {
137
+ const query = readString(args.query);
138
+ const limit = readLimit(args.limit, 200);
139
+ return commandRegistry.list()
140
+ .filter((command) => commandMatches(command, query))
141
+ .sort((a, b) => a.name.localeCompare(b.name))
142
+ .slice(0, limit)
143
+ .map((command) => describeCommand(command));
144
+ }
145
+
146
+ export function describeHarnessCommand(commandRegistry: CommandRegistry, args: AgentHarnessCommandCatalogArgs): Record<string, unknown> | null {
147
+ const detail = resolveCommandDetail(commandRegistry, args);
148
+ return detail ? describeCommand(detail.command, detail.lookup) : null;
149
+ }
@@ -23,11 +23,11 @@ export const AGENT_HARNESS_PARAMETER_PROPERTIES = {
23
23
  },
24
24
  query: {
25
25
  type: 'string',
26
- description: 'Search text for command, setting, tool, or UI surface catalogs.',
26
+ description: 'Search text for command, setting, tool, or UI surface catalogs, or a slash-command lookup for mode command.',
27
27
  },
28
28
  command: {
29
29
  type: 'string',
30
- description: 'Full slash command string for mode run_command, for example "/settings get provider.model". In cli_command mode this may also hold a top-level CLI string such as "goodvibes-agent status --json".',
30
+ description: 'Full slash command string for mode command or run_command, for example "/settings get provider.model". In cli_command mode this may also hold a top-level CLI string such as "goodvibes-agent status --json".',
31
31
  },
32
32
  cliCommand: {
33
33
  type: 'string',
@@ -90,7 +90,7 @@ export const AGENT_HARNESS_PARAMETER_PROPERTIES = {
90
90
  },
91
91
  target: {
92
92
  type: 'string',
93
- description: 'Optional lookup target, such as a model-picker target, settings key, model tool name, or connected-host capability id.',
93
+ description: 'Optional lookup target, such as a model-picker target, slash command root or invocation, settings key, model tool name, or connected-host capability id.',
94
94
  },
95
95
  capabilityId: {
96
96
  type: 'string',
@@ -1,6 +1,6 @@
1
1
  import type { Tool } from '@pellux/goodvibes-sdk/platform/types';
2
2
  import type { ToolRegistry } from '@pellux/goodvibes-sdk/platform/tools';
3
- import type { CommandContext, CommandRegistry, SlashCommand } from '../input/command-registry.ts';
3
+ import type { CommandContext, CommandRegistry } from '../input/command-registry.ts';
4
4
  import { buildAgentWorkspaceCommandEditorSubmission, isAgentWorkspaceCommandEditorKind } from '../input/agent-workspace-command-editor.ts';
5
5
  import { createAgentWorkspaceEditor } from '../input/agent-workspace-activation.ts';
6
6
  import { AGENT_WORKSPACE_CATEGORIES } from '../input/agent-workspace-categories.ts';
@@ -18,6 +18,7 @@ import type {
18
18
  } from '../input/agent-workspace-types.ts';
19
19
  import { parseSlashCommand } from '../input/slash-command-parser.ts';
20
20
  import { blockedHarnessCliCommandTokens, describeHarnessCliCommand, listHarnessCliCommands, totalHarnessCliCommands } from './agent-harness-cli-metadata.ts';
21
+ import { describeHarnessCommand, listHarnessCommands } from './agent-harness-command-catalog.ts';
21
22
  import { describeHarnessKeybinding, listHarnessKeybindings, listHarnessShortcuts, resetHarnessKeybinding, setHarnessKeybinding, totalHarnessKeybindings, totalHarnessShortcuts } from './agent-harness-keybinding-metadata.ts';
22
23
  import { describeHarnessPanel, listHarnessPanels, openHarnessPanel, totalHarnessPanels } from './agent-harness-panel-metadata.ts';
23
24
  import { connectedHostStatusSummary } from './agent-harness-connected-host-status.ts';
@@ -28,7 +29,6 @@ import { describeHarnessUiSurface, listHarnessUiSurfaces, openHarnessUiSurface,
28
29
  import {
29
30
  connectedHostSummary,
30
31
  describeConnectedHostCapability,
31
- describeCommandPolicy,
32
32
  settingsPolicySummary,
33
33
  } from './agent-harness-metadata.ts';
34
34
  import {
@@ -115,30 +115,6 @@ function output(value: unknown): { readonly success: true; readonly output: stri
115
115
 
116
116
  function error(message: string): { readonly success: false; readonly error: string } { return { success: false, error: message }; }
117
117
 
118
- function commandMatches(command: SlashCommand, query: string): boolean {
119
- if (!query) return true;
120
- const haystack = [
121
- command.name,
122
- ...(command.aliases ?? []),
123
- command.description,
124
- command.usage ?? '',
125
- command.argsHint ?? '',
126
- ].join('\n').toLowerCase();
127
- return haystack.includes(query.toLowerCase());
128
- }
129
-
130
- function describeCommand(command: SlashCommand): Record<string, unknown> {
131
- return {
132
- name: command.name,
133
- slash: `/${command.name}`,
134
- aliases: command.aliases ?? [],
135
- description: command.description,
136
- usage: command.usage ?? '',
137
- argsHint: command.argsHint ?? command.usage ?? '',
138
- policy: describeCommandPolicy(command.name),
139
- };
140
- }
141
-
142
118
  function allWorkspaceActions(): ReadonlyArray<{
143
119
  readonly category: AgentWorkspaceCategory;
144
120
  readonly action: AgentWorkspaceAction;
@@ -276,16 +252,6 @@ function findWorkspaceAction(args: AgentHarnessToolArgs): { readonly category: A
276
252
  }) ?? null;
277
253
  }
278
254
 
279
- function listCommands(commandRegistry: CommandRegistry, args: AgentHarnessToolArgs): readonly Record<string, unknown>[] {
280
- const query = readString(args.query);
281
- const limit = readLimit(args.limit, 200);
282
- return commandRegistry.list()
283
- .filter((command) => commandMatches(command, query))
284
- .sort((a, b) => a.name.localeCompare(b.name))
285
- .slice(0, limit)
286
- .map(describeCommand);
287
- }
288
-
289
255
  function requireConfirmedAction(args: AgentHarnessToolArgs, action: string): string | null {
290
256
  const explicitUserRequest = readString(args.explicitUserRequest);
291
257
  if (!explicitUserRequest) return `${action} requires explicitUserRequest with the user's exact request or a short faithful summary.`;
@@ -551,7 +517,7 @@ export function createAgentHarnessTool(deps: AgentHarnessToolDeps): Tool {
551
517
  panels: 'Use mode:"panels" and mode:"panel" to inspect built-in panel catalog/open state; use mode:"open_panel" with confirm:true plus explicitUserRequest to route a visible panel/workspace change.',
552
518
  uiSurfaces: 'Use mode:"ui_surfaces" and mode:"ui_surface" to inspect modal/overlay/picker/workspace surfaces; use mode:"open_ui_surface" with confirm:true plus explicitUserRequest to route visible UI navigation.',
553
519
  shortcuts: 'Use mode:"shortcuts" to inspect fixed shortcuts plus configurable keybindings. Use mode:"set_keybinding" and mode:"reset_keybinding" with confirm:true plus explicitUserRequest to edit the same config file the user edits.',
554
- slashCommands: 'Use mode:"commands" and mode:"command" to inspect; use mode:"run_command" with confirm:true plus explicitUserRequest to execute.',
520
+ slashCommands: 'Use mode:"commands" to list slash commands and mode:"command" with command, commandName, target, or query to inspect one command; use mode:"run_command" with confirm:true plus explicitUserRequest to execute.',
555
521
  workspace: 'Use mode:"workspace_actions" to list and mode:"workspace_action" for editor schemas; set includeParameters:true on workspace_actions to inline editor schemas.',
556
522
  settings: 'Use mode:"settings", mode:"get_setting", mode:"set_setting", and mode:"reset_setting".',
557
523
  tools: 'Use mode:"tools" to list first-class model tools, or mode:"tool" with toolName, target, or query to inspect one schema.',
@@ -623,13 +589,15 @@ export function createAgentHarnessTool(deps: AgentHarnessToolDeps): Tool {
623
589
  return output(resetHarnessKeybinding(deps.commandContext, args));
624
590
  }
625
591
  if (args.mode === 'commands') {
626
- const commands = listCommands(deps.commandRegistry, args);
592
+ const commands = listHarnessCommands(deps.commandRegistry, args);
627
593
  return output({ commands, returned: commands.length, total: deps.commandRegistry.list().length });
628
594
  }
629
595
  if (args.mode === 'command') {
630
- const name = readString(args.commandName).replace(/^\//, '');
631
- const command = name ? deps.commandRegistry.get(name) : null;
632
- return command ? output(describeCommand(command)) : error(`Unknown slash command /${name || '<missing>'}.`);
596
+ const detail = describeHarnessCommand(deps.commandRegistry, args);
597
+ const query = readString(args.command || args.commandName || args.target || args.query);
598
+ return detail
599
+ ? output(detail)
600
+ : error(`Unknown slash command ${query || '<missing>'}. Use mode:"commands" to inspect available commands.`);
633
601
  }
634
602
  if (args.mode === 'run_command') return runCommand(deps, args);
635
603
  if (args.mode === 'settings') {
package/src/version.ts CHANGED
@@ -6,7 +6,7 @@ import { join } from 'node:path';
6
6
  // The prebuild script updates the fallback value before compilation.
7
7
  // Uses import.meta.dir (Bun) to locate package.json relative to this file,
8
8
  // which is correct regardless of the process working directory.
9
- let _version = '1.0.14';
9
+ let _version = '1.0.15';
10
10
  let _sdkVersion = '0.33.35';
11
11
  try {
12
12
  const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8')) as {