@pellux/goodvibes-agent 1.0.13 → 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,18 @@
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
+
11
+ ## 1.0.14 - 2026-06-03
12
+
13
+ - Add agent_harness mode tool to inspect one first-class model tool schema by toolName, target, or query.
14
+ - Keep broad tools discovery lightweight while making individual model tool parameters, side effects, concurrency, and streaming/progress support directly inspectable.
15
+ - Refresh package-facing docs and focused harness coverage for the updated model tool discovery surface.
16
+
5
17
  ## 1.0.13 - 2026-06-03
6
18
 
7
19
  - Add connected_host_capability to inspect one allowed or blocked connected-host capability by id, target, or query with related route families and boundary text.
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, 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, 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.13";
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"));
@@ -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,
@@ -880710,6 +880830,24 @@ function listHarnessModelTools(toolRegistry, args2) {
880710
880830
  ...includeParameters ? { parameters: tool2.parameters } : {}
880711
880831
  }));
880712
880832
  }
880833
+ function describeHarnessModelTool(toolRegistry, args2) {
880834
+ const query2 = readString56(args2.toolName || args2.target || args2.query).toLowerCase();
880835
+ if (!query2)
880836
+ return null;
880837
+ const tool2 = toolRegistry.getToolDefinitions().find((candidate) => candidate.name.toLowerCase() === query2 || candidate.name.toLowerCase().includes(query2));
880838
+ if (!tool2)
880839
+ return null;
880840
+ return {
880841
+ name: tool2.name,
880842
+ description: tool2.description,
880843
+ sideEffects: tool2.sideEffects ?? [],
880844
+ concurrency: tool2.concurrency ?? "parallel",
880845
+ supportsProgress: tool2.supportsProgress ?? false,
880846
+ supportsStreamingOutput: tool2.supportsStreamingOutput ?? false,
880847
+ parameters: tool2.parameters,
880848
+ policy: "This is a first-class model tool definition. Use the returned JSON schema directly; mutating or external side-effect tools still require the explicit confirmation arguments defined by that tool."
880849
+ };
880850
+ }
880713
880851
 
880714
880852
  // src/tools/agent-harness-tool-schema.ts
880715
880853
  var AGENT_HARNESS_MODES = [
@@ -880740,6 +880878,7 @@ var AGENT_HARNESS_MODES = [
880740
880878
  "workspace_action",
880741
880879
  "run_workspace_action",
880742
880880
  "tools",
880881
+ "tool",
880743
880882
  "connected_host",
880744
880883
  "connected_host_status",
880745
880884
  "connected_host_capability"
@@ -880758,11 +880897,11 @@ var AGENT_HARNESS_PARAMETER_PROPERTIES = {
880758
880897
  },
880759
880898
  query: {
880760
880899
  type: "string",
880761
- 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."
880762
880901
  },
880763
880902
  command: {
880764
880903
  type: "string",
880765
- 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".'
880766
880905
  },
880767
880906
  cliCommand: {
880768
880907
  type: "string",
@@ -880825,12 +880964,16 @@ var AGENT_HARNESS_PARAMETER_PROPERTIES = {
880825
880964
  },
880826
880965
  target: {
880827
880966
  type: "string",
880828
- description: "Optional UI target, such as a model-picker target or settings target key."
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."
880829
880968
  },
880830
880969
  capabilityId: {
880831
880970
  type: "string",
880832
880971
  description: "Connected-host allowed or blocked capability id for mode connected_host_capability, such as agent-knowledge-read or connected-host-lifecycle."
880833
880972
  },
880973
+ toolName: {
880974
+ type: "string",
880975
+ description: "First-class model tool name for mode tool, such as agent_harness or agent_local_registry."
880976
+ },
880834
880977
  category: {
880835
880978
  type: "string",
880836
880979
  description: "Setting category filter such as provider, behavior, tools, ui, tts, permissions, automation, or surfaces."
@@ -880962,10 +881105,10 @@ async function openTtsVoicePicker(ctx, providerArg) {
880962
881105
  }
880963
881106
 
880964
881107
  // src/tools/agent-harness-ui-surface-metadata.ts
880965
- function readString56(value) {
881108
+ function readString57(value) {
880966
881109
  return typeof value === "string" ? value.trim() : "";
880967
881110
  }
880968
- function readLimit9(value, fallback) {
881111
+ function readLimit10(value, fallback) {
880969
881112
  const parsed = typeof value === "string" && value.trim() ? Number(value) : value;
880970
881113
  if (typeof parsed !== "number" || !Number.isFinite(parsed))
880971
881114
  return fallback;
@@ -880988,35 +881131,35 @@ function opened(surface, extra = {}) {
880988
881131
  };
880989
881132
  }
880990
881133
  function optionalModelTarget(args2) {
880991
- const target = readString56(args2.target);
881134
+ const target = readString57(args2.target);
880992
881135
  return target === "main" || target === "helper" || target === "tool" || target === "tts" ? target : undefined;
880993
881136
  }
880994
881137
  function optionalOnboardingMode(args2) {
880995
- const target = readString56(args2.target);
881138
+ const target = readString57(args2.target);
880996
881139
  return target === "new" || target === "edit" || target === "reopen" ? target : undefined;
880997
881140
  }
880998
881141
  function workspaceCategory(args2) {
880999
- return readString56(args2.categoryId || args2.category || args2.target) || undefined;
881142
+ return readString57(args2.categoryId || args2.category || args2.target) || undefined;
881000
881143
  }
881001
881144
  function settingsTarget(args2) {
881002
- return readString56(args2.target || args2.key || args2.prefix) || undefined;
881145
+ return readString57(args2.target || args2.key || args2.prefix) || undefined;
881003
881146
  }
881004
881147
  function providerTarget(args2) {
881005
- return readString56(args2.target || args2.key || args2.prefix) || undefined;
881148
+ return readString57(args2.target || args2.key || args2.prefix) || undefined;
881006
881149
  }
881007
881150
  function optionalPane(args2) {
881008
- const pane = readString56(args2.pane);
881151
+ const pane = readString57(args2.pane);
881009
881152
  return pane === "top" || pane === "bottom" ? pane : undefined;
881010
881153
  }
881011
881154
  function surfaceInputText(args2) {
881012
- 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;
881013
881156
  }
881014
881157
  function filePickerOptions(args2) {
881015
- const target = readString56(args2.target).toLowerCase();
881158
+ const target = readString57(args2.target).toLowerCase();
881016
881159
  const injectMode = target === "inject" || target === "inject-mode" || target === "!@";
881017
881160
  return {
881018
881161
  injectMode,
881019
- 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
881020
881163
  };
881021
881164
  }
881022
881165
  function openAgentWorkspaceCategory(context, surface, categoryId, extra = {}) {
@@ -881592,19 +881735,19 @@ function totalHarnessUiSurfaces() {
881592
881735
  return UI_SURFACES.length;
881593
881736
  }
881594
881737
  function listHarnessUiSurfaces(context, args2) {
881595
- const query2 = readString56(args2.query);
881596
- const limit3 = readLimit9(args2.limit, 200);
881738
+ const query2 = readString57(args2.query);
881739
+ const limit3 = readLimit10(args2.limit, 200);
881597
881740
  return UI_SURFACES.map((surface) => describeSurface(context, surface)).filter((surface) => surfaceMatches(surface, query2)).slice(0, limit3);
881598
881741
  }
881599
881742
  function describeHarnessUiSurface(context, args2) {
881600
- const surfaceId = readString56(args2.surfaceId || args2.query);
881743
+ const surfaceId = readString57(args2.surfaceId || args2.query);
881601
881744
  if (!surfaceId)
881602
881745
  return null;
881603
881746
  const surface = UI_SURFACES.find((entry) => entry.id === surfaceId || entry.label.toLowerCase() === surfaceId.toLowerCase());
881604
881747
  return surface ? describeSurface(context, surface) : null;
881605
881748
  }
881606
881749
  async function openHarnessUiSurface(context, args2) {
881607
- const surfaceId = readString56(args2.surfaceId || args2.query);
881750
+ const surfaceId = readString57(args2.surfaceId || args2.query);
881608
881751
  const surface = UI_SURFACES.find((entry) => entry.id === surfaceId || entry.label.toLowerCase() === surfaceId.toLowerCase());
881609
881752
  if (!surface) {
881610
881753
  return {
@@ -881624,16 +881767,16 @@ async function openHarnessUiSurface(context, args2) {
881624
881767
  function isMode(value) {
881625
881768
  return typeof value === "string" && AGENT_HARNESS_MODES.includes(value);
881626
881769
  }
881627
- function readString57(value) {
881770
+ function readString58(value) {
881628
881771
  return typeof value === "string" ? value.trim() : "";
881629
881772
  }
881630
- function readLimit10(value, fallback) {
881773
+ function readLimit11(value, fallback) {
881631
881774
  const parsed = typeof value === "string" && value.trim() ? Number(value) : value;
881632
881775
  if (typeof parsed !== "number" || !Number.isFinite(parsed))
881633
881776
  return fallback;
881634
881777
  return Math.max(1, Math.min(500, Math.trunc(parsed)));
881635
881778
  }
881636
- function readStringArray12(value) {
881779
+ function readStringArray13(value) {
881637
881780
  if (!Array.isArray(value))
881638
881781
  return [];
881639
881782
  return value.map((entry) => typeof entry === "string" ? entry : String(entry));
@@ -881652,30 +881795,6 @@ function output7(value) {
881652
881795
  function error52(message) {
881653
881796
  return { success: false, error: message };
881654
881797
  }
881655
- function commandMatches(command8, query2) {
881656
- if (!query2)
881657
- return true;
881658
- const haystack = [
881659
- command8.name,
881660
- ...command8.aliases ?? [],
881661
- command8.description,
881662
- command8.usage ?? "",
881663
- command8.argsHint ?? ""
881664
- ].join(`
881665
- `).toLowerCase();
881666
- return haystack.includes(query2.toLowerCase());
881667
- }
881668
- function describeCommand(command8) {
881669
- return {
881670
- name: command8.name,
881671
- slash: `/${command8.name}`,
881672
- aliases: command8.aliases ?? [],
881673
- description: command8.description,
881674
- usage: command8.usage ?? "",
881675
- argsHint: command8.argsHint ?? command8.usage ?? "",
881676
- policy: describeCommandPolicy(command8.name)
881677
- };
881678
- }
881679
881798
  function allWorkspaceActions() {
881680
881799
  return AGENT_WORKSPACE_CATEGORIES.flatMap((category) => category.actions.map((action2) => ({ category, action: action2 })));
881681
881800
  }
@@ -881708,7 +881827,7 @@ function describeWorkspaceEditor(editor) {
881708
881827
  }
881709
881828
  function selectedRoutineFromArgs(snapshot, args2) {
881710
881829
  const fields = readFieldMap3(args2.fields);
881711
- const routineId = readString57(args2.recordId) || readString57(fields.routineId) || readString57(fields.id);
881830
+ const routineId = readString58(args2.recordId) || readString58(fields.routineId) || readString58(fields.id);
881712
881831
  if (!routineId)
881713
881832
  return null;
881714
881833
  return snapshot.localRoutines.find((routine) => routine.id === routineId || routine.name.toLowerCase() === routineId.toLowerCase()) ?? null;
@@ -881776,17 +881895,17 @@ function localEditorModelExecution(editorKind) {
881776
881895
  return "Use the command field, editor schema, or a first-class Agent model tool when available.";
881777
881896
  }
881778
881897
  function listWorkspaceActions(deps, args2) {
881779
- const query2 = readString57(args2.query);
881780
- const categoryId = readString57(args2.categoryId || args2.category);
881781
- 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);
881782
881901
  const includeEditor = args2.includeParameters === true;
881783
881902
  const editorContext = includeEditor ? buildWorkspaceEditorContext(deps.commandContext, args2) : null;
881784
881903
  const source = query2 ? searchAgentWorkspaceActions(AGENT_WORKSPACE_CATEGORIES, query2).map((result2) => ({ category: result2.category, action: result2.action })) : allWorkspaceActions();
881785
881904
  return source.filter((entry) => !categoryId || entry.category.id === categoryId).slice(0, limit3).map((entry) => describeWorkspaceAction(entry.category, entry.action, { includeEditor, editorContext }));
881786
881905
  }
881787
881906
  function findWorkspaceAction(args2) {
881788
- const actionId = readString57(args2.actionId || args2.query);
881789
- const categoryId = readString57(args2.categoryId || args2.category);
881907
+ const actionId = readString58(args2.actionId || args2.query);
881908
+ const categoryId = readString58(args2.categoryId || args2.category);
881790
881909
  if (!actionId)
881791
881910
  return null;
881792
881911
  return allWorkspaceActions().find((entry) => {
@@ -881795,13 +881914,8 @@ function findWorkspaceAction(args2) {
881795
881914
  return entry.action.id === actionId || entry.action.label.toLowerCase() === actionId.toLowerCase();
881796
881915
  }) ?? null;
881797
881916
  }
881798
- function listCommands(commandRegistry, args2) {
881799
- const query2 = readString57(args2.query);
881800
- const limit3 = readLimit10(args2.limit, 200);
881801
- return commandRegistry.list().filter((command8) => commandMatches(command8, query2)).sort((a4, b3) => a4.name.localeCompare(b3.name)).slice(0, limit3).map(describeCommand);
881802
- }
881803
881917
  function requireConfirmedAction(args2, action2) {
881804
- const explicitUserRequest = readString57(args2.explicitUserRequest);
881918
+ const explicitUserRequest = readString58(args2.explicitUserRequest);
881805
881919
  if (!explicitUserRequest)
881806
881920
  return `${action2} requires explicitUserRequest with the user's exact request or a short faithful summary.`;
881807
881921
  if (args2.confirm !== true)
@@ -881809,7 +881923,7 @@ function requireConfirmedAction(args2, action2) {
881809
881923
  return null;
881810
881924
  }
881811
881925
  function commandFromArgs(args2) {
881812
- const rawCommand = readString57(args2.command);
881926
+ const rawCommand = readString58(args2.command);
881813
881927
  if (rawCommand) {
881814
881928
  const parsed = parseSlashCommand(rawCommand);
881815
881929
  if (!parsed.name)
@@ -881819,10 +881933,10 @@ function commandFromArgs(args2) {
881819
881933
  args: parsed.args
881820
881934
  };
881821
881935
  }
881822
- const commandName = readString57(args2.commandName).replace(/^\//, "");
881936
+ const commandName = readString58(args2.commandName).replace(/^\//, "");
881823
881937
  if (!commandName)
881824
881938
  return null;
881825
- const commandArgs = readStringArray12(args2.args);
881939
+ const commandArgs = readStringArray13(args2.args);
881826
881940
  return {
881827
881941
  name: commandName,
881828
881942
  args: commandArgs
@@ -882010,7 +882124,7 @@ function createAgentHarnessTool(deps) {
882010
882124
  name: "agent_harness",
882011
882125
  description: [
882012
882126
  "Discover and operate the GoodVibes Agent harness from the main conversation.",
882013
- "Use this tool to inspect Agent workspace actions, built-in panels, top-level CLI mirrors, UI surfaces, keybindings, slash commands with policy metadata, model tools, connected-host capabilities or one connected-host capability detail, and Agent settings, or to invoke a workspace action/command through the same in-process command registry the user uses in the TUI.",
882127
+ "Use this tool to inspect Agent workspace actions, built-in panels, top-level CLI mirrors, UI surfaces, keybindings, slash commands with policy metadata, model tools or one model tool schema, connected-host capabilities or one connected-host capability detail, and Agent settings, or to invoke a workspace action/command through the same in-process command registry the user uses in the TUI.",
882014
882128
  "Discovery modes are read-only. Setting/keybinding writes, resets, UI routing, slash command invocation, and workspace action invocation require confirm:true plus explicitUserRequest.",
882015
882129
  "This tool preserves Agent product boundaries: connected-host lifecycle and listener posture stay externally owned, connected-host mode reports allowed and blocked route families, and secret-backed settings store raw values through the secret manager while config receives only a secret reference."
882016
882130
  ].join(" "),
@@ -882046,10 +882160,10 @@ function createAgentHarnessTool(deps) {
882046
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.',
882047
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.',
882048
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.',
882049
- 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.',
882050
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.',
882051
882165
  settings: 'Use mode:"settings", mode:"get_setting", mode:"set_setting", and mode:"reset_setting".',
882052
- tools: 'Use mode:"tools" with includeParameters:true to inspect first-class model tool schemas.',
882166
+ tools: 'Use mode:"tools" to list first-class model tools, or mode:"tool" with toolName, target, or query to inspect one schema.',
882053
882167
  connectedHost: 'Use mode:"connected_host" for the connected-host capability map and blocked boundaries. Use mode:"connected_host_capability" with capabilityId, target, or query for one allowed or blocked capability.',
882054
882168
  connectedHostStatus: 'Use mode:"connected_host_status" for live read-only host reachability, SDK compatibility, token posture, and Agent Knowledge route readiness.'
882055
882169
  },
@@ -882081,7 +882195,7 @@ function createAgentHarnessTool(deps) {
882081
882195
  }
882082
882196
  if (args2.mode === "panel") {
882083
882197
  const panel = describeHarnessPanel(deps.commandContext, args2);
882084
- 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>"}.`);
882085
882199
  }
882086
882200
  if (args2.mode === "open_panel") {
882087
882201
  const confirmationError2 = requireConfirmedAction(args2, "Panel routing");
@@ -882095,7 +882209,7 @@ function createAgentHarnessTool(deps) {
882095
882209
  }
882096
882210
  if (args2.mode === "ui_surface") {
882097
882211
  const surface = describeHarnessUiSurface(deps.commandContext, args2);
882098
- 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>"}.`);
882099
882213
  }
882100
882214
  if (args2.mode === "open_ui_surface") {
882101
882215
  const confirmationError2 = requireConfirmedAction(args2, "UI surface routing");
@@ -882109,7 +882223,7 @@ function createAgentHarnessTool(deps) {
882109
882223
  return output7(listHarnessKeybindings(deps.commandContext, args2));
882110
882224
  if (args2.mode === "keybinding") {
882111
882225
  const binding = describeHarnessKeybinding(deps.commandContext, args2);
882112
- 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>"}.`);
882113
882227
  }
882114
882228
  if (args2.mode === "set_keybinding") {
882115
882229
  const confirmationError2 = requireConfirmedAction(args2, "Keybinding mutation");
@@ -882124,28 +882238,28 @@ function createAgentHarnessTool(deps) {
882124
882238
  return output7(resetHarnessKeybinding(deps.commandContext, args2));
882125
882239
  }
882126
882240
  if (args2.mode === "commands") {
882127
- const commands3 = listCommands(deps.commandRegistry, args2);
882241
+ const commands3 = listHarnessCommands(deps.commandRegistry, args2);
882128
882242
  return output7({ commands: commands3, returned: commands3.length, total: deps.commandRegistry.list().length });
882129
882243
  }
882130
882244
  if (args2.mode === "command") {
882131
- const name51 = readString57(args2.commandName).replace(/^\//, "");
882132
- const command8 = name51 ? deps.commandRegistry.get(name51) : null;
882133
- 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.`);
882134
882248
  }
882135
882249
  if (args2.mode === "run_command")
882136
882250
  return runCommand2(deps, args2);
882137
882251
  if (args2.mode === "settings") {
882138
882252
  const settings = listHarnessSettings(deps.commandContext.platform.configManager, {
882139
- category: readString57(args2.category) || undefined,
882140
- prefix: readString57(args2.prefix) || undefined,
882141
- query: readString57(args2.query) || undefined,
882253
+ category: readString58(args2.category) || undefined,
882254
+ prefix: readString58(args2.prefix) || undefined,
882255
+ query: readString58(args2.query) || undefined,
882142
882256
  includeHidden: args2.includeHidden === true,
882143
- limit: readLimit10(args2.limit, 100)
882257
+ limit: readLimit11(args2.limit, 100)
882144
882258
  });
882145
882259
  return output7({ settings, returned: settings.length, policy: settingsPolicySummary() });
882146
882260
  }
882147
882261
  if (args2.mode === "get_setting") {
882148
- const key = readString57(args2.key);
882262
+ const key = readString58(args2.key);
882149
882263
  const setting = getHarnessSetting(deps.commandContext.platform.configManager, key);
882150
882264
  return setting ? output7(setting) : error52(`Unknown setting ${key || "<missing>"}.`);
882151
882265
  }
@@ -882155,7 +882269,7 @@ function createAgentHarnessTool(deps) {
882155
882269
  return error52(confirmationError2);
882156
882270
  if (args2.value === undefined)
882157
882271
  return error52("set_setting requires value.");
882158
- const key = readString57(args2.key);
882272
+ const key = readString58(args2.key);
882159
882273
  const result2 = await setHarnessSetting(deps.commandContext.platform.configManager, deps.commandContext.platform.secretsManager, key, args2.value);
882160
882274
  return output7(result2);
882161
882275
  }
@@ -882163,7 +882277,7 @@ function createAgentHarnessTool(deps) {
882163
882277
  const confirmationError2 = requireConfirmedAction(args2, "Setting reset");
882164
882278
  if (confirmationError2)
882165
882279
  return error52(confirmationError2);
882166
- const key = readString57(args2.key);
882280
+ const key = readString58(args2.key);
882167
882281
  const result2 = await resetHarnessSetting(deps.commandContext.platform.configManager, deps.commandContext.platform.secretsManager, key);
882168
882282
  return output7(result2);
882169
882283
  }
@@ -882180,7 +882294,7 @@ function createAgentHarnessTool(deps) {
882180
882294
  if (args2.mode === "workspace_action") {
882181
882295
  const found = findWorkspaceAction(args2);
882182
882296
  const editorContext = buildWorkspaceEditorContext(deps.commandContext, args2);
882183
- 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>"}.`);
882184
882298
  }
882185
882299
  if (args2.mode === "run_workspace_action")
882186
882300
  return runWorkspaceAction(deps, args2);
@@ -882188,10 +882302,15 @@ function createAgentHarnessTool(deps) {
882188
882302
  const tools3 = listHarnessModelTools(deps.toolRegistry, args2);
882189
882303
  return output7({ tools: tools3, returned: tools3.length, total: deps.toolRegistry.getToolDefinitions().length });
882190
882304
  }
882305
+ if (args2.mode === "tool") {
882306
+ const query2 = readString58(args2.toolName || args2.target || args2.query);
882307
+ const tool2 = describeHarnessModelTool(deps.toolRegistry, args2);
882308
+ return tool2 ? output7(tool2) : error52(`Unknown model tool ${query2 || "<missing>"}. Use mode:"tools" to inspect available model tools.`);
882309
+ }
882191
882310
  if (args2.mode === "connected_host")
882192
882311
  return output7(connectedHostSummary(deps.commandContext, deps.toolRegistry));
882193
882312
  if (args2.mode === "connected_host_capability") {
882194
- const query2 = readString57(args2.capabilityId || args2.target || args2.query);
882313
+ const query2 = readString58(args2.capabilityId || args2.target || args2.query);
882195
882314
  const capability = describeConnectedHostCapability(deps.toolRegistry, query2);
882196
882315
  return capability ? output7(capability) : error52(`Unknown connected-host capability ${query2 || "<missing>"}. Use mode:"connected_host" to inspect allowed and blocked capability ids.`);
882197
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, 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, 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, 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` can inline model tool JSON schemas with `includeParameters:true`; `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.13",
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
+ }
@@ -2,6 +2,8 @@ import type { ToolRegistry } from '@pellux/goodvibes-sdk/platform/tools';
2
2
 
3
3
  export interface AgentHarnessModelToolCatalogArgs {
4
4
  readonly query?: unknown;
5
+ readonly toolName?: unknown;
6
+ readonly target?: unknown;
5
7
  readonly includeParameters?: unknown;
6
8
  readonly limit?: unknown;
7
9
  }
@@ -34,3 +36,21 @@ export function listHarnessModelTools(toolRegistry: ToolRegistry, args: AgentHar
34
36
  ...(includeParameters ? { parameters: tool.parameters } : {}),
35
37
  }));
36
38
  }
39
+
40
+ export function describeHarnessModelTool(toolRegistry: ToolRegistry, args: AgentHarnessModelToolCatalogArgs): Record<string, unknown> | null {
41
+ const query = readString(args.toolName || args.target || args.query).toLowerCase();
42
+ if (!query) return null;
43
+ const tool = toolRegistry.getToolDefinitions()
44
+ .find((candidate) => candidate.name.toLowerCase() === query || candidate.name.toLowerCase().includes(query));
45
+ if (!tool) return null;
46
+ return {
47
+ name: tool.name,
48
+ description: tool.description,
49
+ sideEffects: tool.sideEffects ?? [],
50
+ concurrency: tool.concurrency ?? 'parallel',
51
+ supportsProgress: tool.supportsProgress ?? false,
52
+ supportsStreamingOutput: tool.supportsStreamingOutput ?? false,
53
+ parameters: tool.parameters,
54
+ policy: 'This is a first-class model tool definition. Use the returned JSON schema directly; mutating or external side-effect tools still require the explicit confirmation arguments defined by that tool.',
55
+ };
56
+ }
@@ -4,7 +4,7 @@ export const AGENT_HARNESS_MODES = [
4
4
  'shortcuts', 'keybindings', 'keybinding', 'set_keybinding', 'reset_keybinding',
5
5
  'commands', 'command', 'run_command', 'settings', 'get_setting', 'set_setting',
6
6
  'reset_setting', 'workspace', 'workspace_categories', 'workspace_actions',
7
- 'workspace_action', 'run_workspace_action', 'tools', 'connected_host', 'connected_host_status',
7
+ 'workspace_action', 'run_workspace_action', 'tools', 'tool', 'connected_host', 'connected_host_status',
8
8
  'connected_host_capability',
9
9
  ] as const;
10
10
 
@@ -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,12 +90,16 @@ export const AGENT_HARNESS_PARAMETER_PROPERTIES = {
90
90
  },
91
91
  target: {
92
92
  type: 'string',
93
- description: 'Optional UI target, such as a model-picker target or settings target key.',
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',
97
97
  description: 'Connected-host allowed or blocked capability id for mode connected_host_capability, such as agent-knowledge-read or connected-host-lifecycle.',
98
98
  },
99
+ toolName: {
100
+ type: 'string',
101
+ description: 'First-class model tool name for mode tool, such as agent_harness or agent_local_registry.',
102
+ },
99
103
  category: {
100
104
  type: 'string',
101
105
  description: 'Setting category filter such as provider, behavior, tools, ui, tts, permissions, automation, or surfaces.',
@@ -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,17 +18,17 @@ 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';
24
25
  import { describeLocalWorkspaceModelExecution, runLocalWorkspaceAction, runLocalWorkspaceEditorAction } from './agent-harness-local-operations.ts';
25
- import { listHarnessModelTools } from './agent-harness-model-tool-catalog.ts';
26
+ import { describeHarnessModelTool, listHarnessModelTools } from './agent-harness-model-tool-catalog.ts';
26
27
  import { AGENT_HARNESS_MODES, AGENT_HARNESS_PARAMETER_PROPERTIES } from './agent-harness-tool-schema.ts';
27
28
  import { describeHarnessUiSurface, listHarnessUiSurfaces, openHarnessUiSurface, totalHarnessUiSurfaces } from './agent-harness-ui-surface-metadata.ts';
28
29
  import {
29
30
  connectedHostSummary,
30
31
  describeConnectedHostCapability,
31
- describeCommandPolicy,
32
32
  settingsPolicySummary,
33
33
  } from './agent-harness-metadata.ts';
34
34
  import {
@@ -60,6 +60,7 @@ interface AgentHarnessToolArgs {
60
60
  readonly value?: unknown;
61
61
  readonly target?: unknown;
62
62
  readonly capabilityId?: unknown;
63
+ readonly toolName?: unknown;
63
64
  readonly category?: unknown;
64
65
  readonly prefix?: unknown;
65
66
  readonly includeHidden?: unknown;
@@ -114,30 +115,6 @@ function output(value: unknown): { readonly success: true; readonly output: stri
114
115
 
115
116
  function error(message: string): { readonly success: false; readonly error: string } { return { success: false, error: message }; }
116
117
 
117
- function commandMatches(command: SlashCommand, query: string): boolean {
118
- if (!query) return true;
119
- const haystack = [
120
- command.name,
121
- ...(command.aliases ?? []),
122
- command.description,
123
- command.usage ?? '',
124
- command.argsHint ?? '',
125
- ].join('\n').toLowerCase();
126
- return haystack.includes(query.toLowerCase());
127
- }
128
-
129
- function describeCommand(command: SlashCommand): Record<string, unknown> {
130
- return {
131
- name: command.name,
132
- slash: `/${command.name}`,
133
- aliases: command.aliases ?? [],
134
- description: command.description,
135
- usage: command.usage ?? '',
136
- argsHint: command.argsHint ?? command.usage ?? '',
137
- policy: describeCommandPolicy(command.name),
138
- };
139
- }
140
-
141
118
  function allWorkspaceActions(): ReadonlyArray<{
142
119
  readonly category: AgentWorkspaceCategory;
143
120
  readonly action: AgentWorkspaceAction;
@@ -275,16 +252,6 @@ function findWorkspaceAction(args: AgentHarnessToolArgs): { readonly category: A
275
252
  }) ?? null;
276
253
  }
277
254
 
278
- function listCommands(commandRegistry: CommandRegistry, args: AgentHarnessToolArgs): readonly Record<string, unknown>[] {
279
- const query = readString(args.query);
280
- const limit = readLimit(args.limit, 200);
281
- return commandRegistry.list()
282
- .filter((command) => commandMatches(command, query))
283
- .sort((a, b) => a.name.localeCompare(b.name))
284
- .slice(0, limit)
285
- .map(describeCommand);
286
- }
287
-
288
255
  function requireConfirmedAction(args: AgentHarnessToolArgs, action: string): string | null {
289
256
  const explicitUserRequest = readString(args.explicitUserRequest);
290
257
  if (!explicitUserRequest) return `${action} requires explicitUserRequest with the user's exact request or a short faithful summary.`;
@@ -515,7 +482,7 @@ export function createAgentHarnessTool(deps: AgentHarnessToolDeps): Tool {
515
482
  name: 'agent_harness',
516
483
  description: [
517
484
  'Discover and operate the GoodVibes Agent harness from the main conversation.',
518
- 'Use this tool to inspect Agent workspace actions, built-in panels, top-level CLI mirrors, UI surfaces, keybindings, slash commands with policy metadata, model tools, connected-host capabilities or one connected-host capability detail, and Agent settings, or to invoke a workspace action/command through the same in-process command registry the user uses in the TUI.',
485
+ 'Use this tool to inspect Agent workspace actions, built-in panels, top-level CLI mirrors, UI surfaces, keybindings, slash commands with policy metadata, model tools or one model tool schema, connected-host capabilities or one connected-host capability detail, and Agent settings, or to invoke a workspace action/command through the same in-process command registry the user uses in the TUI.',
519
486
  'Discovery modes are read-only. Setting/keybinding writes, resets, UI routing, slash command invocation, and workspace action invocation require confirm:true plus explicitUserRequest.',
520
487
  'This tool preserves Agent product boundaries: connected-host lifecycle and listener posture stay externally owned, connected-host mode reports allowed and blocked route families, and secret-backed settings store raw values through the secret manager while config receives only a secret reference.',
521
488
  ].join(' '),
@@ -550,10 +517,10 @@ export function createAgentHarnessTool(deps: AgentHarnessToolDeps): Tool {
550
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.',
551
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.',
552
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.',
553
- 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.',
554
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.',
555
522
  settings: 'Use mode:"settings", mode:"get_setting", mode:"set_setting", and mode:"reset_setting".',
556
- tools: 'Use mode:"tools" with includeParameters:true to inspect first-class model tool schemas.',
523
+ tools: 'Use mode:"tools" to list first-class model tools, or mode:"tool" with toolName, target, or query to inspect one schema.',
557
524
  connectedHost: 'Use mode:"connected_host" for the connected-host capability map and blocked boundaries. Use mode:"connected_host_capability" with capabilityId, target, or query for one allowed or blocked capability.',
558
525
  connectedHostStatus: 'Use mode:"connected_host_status" for live read-only host reachability, SDK compatibility, token posture, and Agent Knowledge route readiness.',
559
526
  },
@@ -622,13 +589,15 @@ export function createAgentHarnessTool(deps: AgentHarnessToolDeps): Tool {
622
589
  return output(resetHarnessKeybinding(deps.commandContext, args));
623
590
  }
624
591
  if (args.mode === 'commands') {
625
- const commands = listCommands(deps.commandRegistry, args);
592
+ const commands = listHarnessCommands(deps.commandRegistry, args);
626
593
  return output({ commands, returned: commands.length, total: deps.commandRegistry.list().length });
627
594
  }
628
595
  if (args.mode === 'command') {
629
- const name = readString(args.commandName).replace(/^\//, '');
630
- const command = name ? deps.commandRegistry.get(name) : null;
631
- 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.`);
632
601
  }
633
602
  if (args.mode === 'run_command') return runCommand(deps, args);
634
603
  if (args.mode === 'settings') {
@@ -692,6 +661,13 @@ export function createAgentHarnessTool(deps: AgentHarnessToolDeps): Tool {
692
661
  const tools = listHarnessModelTools(deps.toolRegistry, args);
693
662
  return output({ tools, returned: tools.length, total: deps.toolRegistry.getToolDefinitions().length });
694
663
  }
664
+ if (args.mode === 'tool') {
665
+ const query = readString(args.toolName || args.target || args.query);
666
+ const tool = describeHarnessModelTool(deps.toolRegistry, args);
667
+ return tool
668
+ ? output(tool)
669
+ : error(`Unknown model tool ${query || '<missing>'}. Use mode:"tools" to inspect available model tools.`);
670
+ }
695
671
  if (args.mode === 'connected_host') return output(connectedHostSummary(deps.commandContext, deps.toolRegistry));
696
672
  if (args.mode === 'connected_host_capability') {
697
673
  const query = readString(args.capabilityId || args.target || args.query);
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.13';
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 {