@pellux/goodvibes-agent 1.0.34 → 1.0.36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/CHANGELOG.md +28 -13
  2. package/README.md +11 -8
  3. package/dist/package/main.js +1592 -921
  4. package/docs/README.md +6 -5
  5. package/docs/channels-remote-and-api.md +3 -3
  6. package/docs/connected-host.md +3 -3
  7. package/docs/getting-started.md +20 -12
  8. package/docs/knowledge-artifacts-and-multimodal.md +9 -4
  9. package/docs/project-planning.md +3 -3
  10. package/docs/providers-and-routing.md +2 -2
  11. package/docs/release-and-publishing.md +8 -8
  12. package/docs/tools-and-commands.md +34 -17
  13. package/docs/voice-and-live-tts.md +1 -1
  14. package/package.json +1 -1
  15. package/release/live-verification/live-verification.json +15 -15
  16. package/release/live-verification/live-verification.md +19 -21
  17. package/release/release-notes.md +6 -6
  18. package/release/release-readiness.json +64 -64
  19. package/src/agent/harness-control.ts +35 -9
  20. package/src/agent/reminder-schedule-format.ts +2 -5
  21. package/src/agent/reminder-schedule.ts +4 -11
  22. package/src/agent/routine-schedule-format.ts +3 -6
  23. package/src/agent/routine-schedule-promotion.ts +5 -14
  24. package/src/agent/routine-schedule-receipts.ts +4 -9
  25. package/src/cli/agent-knowledge-command.ts +6 -11
  26. package/src/cli/agent-knowledge-format.ts +4 -9
  27. package/src/cli/agent-knowledge-runtime.ts +7 -14
  28. package/src/cli/external-runtime.ts +3 -46
  29. package/src/cli/help.ts +2 -2
  30. package/src/cli/status.ts +6 -7
  31. package/src/input/agent-workspace-categories.ts +23 -23
  32. package/src/input/agent-workspace-channels.ts +3 -3
  33. package/src/input/agent-workspace-operations-command-editors.ts +1 -1
  34. package/src/input/command-registry.ts +1 -1
  35. package/src/input/commands/compat-runtime.ts +1 -1
  36. package/src/input/commands/planning-runtime.ts +1 -1
  37. package/src/input/commands/runtime-services.ts +1 -1
  38. package/src/input/mcp-workspace.ts +1 -1
  39. package/src/input/submission-router.ts +1 -1
  40. package/src/panels/builtin/shared.ts +1 -1
  41. package/src/panels/project-planning-panel.ts +4 -4
  42. package/src/panels/qr-panel.ts +2 -2
  43. package/src/renderer/conversation-overlays.ts +1 -0
  44. package/src/runtime/bootstrap-command-parts.ts +1 -1
  45. package/src/runtime/bootstrap-core.ts +3 -1
  46. package/src/runtime/bootstrap-hook-bridge.ts +1 -1
  47. package/src/runtime/bootstrap-shell.ts +1 -1
  48. package/src/runtime/bootstrap.ts +7 -4
  49. package/src/runtime/context.ts +2 -2
  50. package/src/runtime/index.ts +88 -5
  51. package/src/runtime/store/state.ts +1 -1
  52. package/src/tools/agent-channel-send-tool.ts +1 -1
  53. package/src/tools/agent-harness-channel-metadata.ts +36 -25
  54. package/src/tools/agent-harness-cli-metadata.ts +19 -1
  55. package/src/tools/agent-harness-command-catalog.ts +15 -4
  56. package/src/tools/agent-harness-connected-host-status.ts +7 -6
  57. package/src/tools/agent-harness-delegation-posture.ts +15 -4
  58. package/src/tools/agent-harness-keybinding-metadata.ts +67 -16
  59. package/src/tools/agent-harness-mcp-metadata.ts +11 -3
  60. package/src/tools/agent-harness-media-posture.ts +25 -14
  61. package/src/tools/agent-harness-metadata.ts +140 -37
  62. package/src/tools/agent-harness-mode-catalog.ts +258 -0
  63. package/src/tools/agent-harness-model-routing.ts +18 -3
  64. package/src/tools/agent-harness-model-tool-catalog.ts +85 -7
  65. package/src/tools/agent-harness-notification-metadata.ts +30 -19
  66. package/src/tools/agent-harness-operator-methods.ts +15 -10
  67. package/src/tools/agent-harness-pairing-posture.ts +17 -5
  68. package/src/tools/agent-harness-panel-metadata.ts +10 -4
  69. package/src/tools/agent-harness-provider-account-metadata.ts +46 -35
  70. package/src/tools/agent-harness-release-evidence.ts +31 -3
  71. package/src/tools/agent-harness-release-readiness.ts +30 -0
  72. package/src/tools/agent-harness-security-posture.ts +18 -4
  73. package/src/tools/agent-harness-service-posture.ts +9 -2
  74. package/src/tools/agent-harness-session-metadata.ts +24 -8
  75. package/src/tools/agent-harness-setup-posture.ts +32 -21
  76. package/src/tools/agent-harness-text.ts +6 -0
  77. package/src/tools/agent-harness-tool-schema.ts +1 -1
  78. package/src/tools/agent-harness-tool.ts +38 -36
  79. package/src/tools/agent-harness-ui-surface-metadata.ts +105 -101
  80. package/src/tools/agent-harness-workspace-actions.ts +64 -3
  81. package/src/tools/agent-knowledge-ingest-tool.ts +1 -1
  82. package/src/tools/agent-knowledge-tool.ts +1 -1
  83. package/src/tools/agent-media-generate-tool.ts +2 -2
  84. package/src/tools/agent-notify-tool.ts +1 -1
  85. package/src/tools/agent-reminder-schedule-tool.ts +1 -1
  86. package/src/tools/tool-definition-compaction.ts +76 -0
  87. package/src/version.ts +2 -8
@@ -1,6 +1,7 @@
1
1
  import type { CommandContext } from '../input/command-registry.ts';
2
2
  import type { AgentWorkspaceChannelStatus } from '../input/agent-workspace-channels.ts';
3
3
  import { buildAgentWorkspaceChannels } from '../input/agent-workspace-channels.ts';
4
+ import { previewHarnessText } from './agent-harness-text.ts';
4
5
 
5
6
  export interface AgentHarnessChannelArgs {
6
7
  readonly channelId?: unknown;
@@ -59,9 +60,14 @@ function describeChannelCandidate(channel: AgentWorkspaceChannelStatus): Record<
59
60
  setupState: channel.setupState,
60
61
  ready: channel.ready,
61
62
  delivery: channel.delivery,
63
+ modelRoute: channelModelRoute(),
62
64
  };
63
65
  }
64
66
 
67
+ function channelModelRoute(): string {
68
+ return 'agent_channel_send or agent_harness mode:"channel"';
69
+ }
70
+
65
71
  function describeChannel(
66
72
  channel: AgentWorkspaceChannelStatus,
67
73
  options: { readonly includeParameters?: boolean; readonly lookup?: Record<string, unknown> } = {},
@@ -82,32 +88,37 @@ function describeChannel(
82
88
  missingConfigKeys: channel.missingRequiredKeys,
83
89
  defaultTargetKeys: channel.defaultTargetKeys,
84
90
  configuredDefaultTargetKeys: channel.configuredDefaultTargetKeys,
91
+ modelRoute: channelModelRoute(),
85
92
  ...(options.lookup ? { lookup: options.lookup } : {}),
86
- ...(options.includeParameters ? {
87
- policy: {
88
- effect: 'read-only',
89
- values: 'Config key names and target key names are shown; secret values and stored target values are never returned.',
90
- delivery: 'Use agent_channel_send only for one explicit, confirmed delivery target requested by the user.',
91
- setup: 'Use connected-host setup/account/policy routes only as read-only diagnostics unless another confirmed first-class tool owns the mutation.',
92
- },
93
- modelAccess: {
94
- sendTool: 'agent_channel_send',
95
- notificationTool: 'agent_notify',
96
- reminderTool: 'agent_reminder_schedule',
97
- slashCommandDetail: `/channels show ${channel.id}`,
98
- readOnlyConnectedRoutes: [
99
- '/channels accounts',
100
- '/channels policies',
101
- '/channels status',
102
- `/channels doctor ${channel.id}`,
103
- `/channels setup ${channel.id}`,
104
- ],
105
- settingsFilter: `agent_harness mode:"settings" prefix:"surfaces.${channel.id}" includeHidden:true`,
106
- connectedHostBoundary: 'agent_harness mode:"connected_host_capability" query:"delivery"',
107
- deliveryTargetShape: 'surface[:route[:label]]',
108
- exampleTarget: `${channel.id}:route:Label`,
109
- },
110
- } : {}),
93
+ ...(options.includeParameters
94
+ ? {
95
+ policy: {
96
+ effect: 'read-only',
97
+ values: 'Config key names and target key names are shown; secret values and stored target values are never returned.',
98
+ delivery: 'Use agent_channel_send only for one explicit, confirmed delivery target requested by the user.',
99
+ setup: 'Use connected-host setup/account/policy routes only as read-only diagnostics unless another confirmed first-class tool owns the mutation.',
100
+ },
101
+ modelAccess: {
102
+ sendTool: 'agent_channel_send',
103
+ notificationTool: 'agent_notify',
104
+ reminderTool: 'agent_reminder_schedule',
105
+ slashCommandDetail: `/channels show ${channel.id}`,
106
+ readOnlyConnectedRoutes: [
107
+ '/channels accounts',
108
+ '/channels policies',
109
+ '/channels status',
110
+ `/channels doctor ${channel.id}`,
111
+ `/channels setup ${channel.id}`,
112
+ ],
113
+ settingsFilter: `agent_harness mode:"settings" prefix:"surfaces.${channel.id}" includeHidden:true`,
114
+ connectedHostBoundary: 'agent_harness mode:"connected_host_capability" query:"delivery"',
115
+ deliveryTargetShape: 'surface[:route[:label]]',
116
+ exampleTarget: `${channel.id}:route:Label`,
117
+ },
118
+ }
119
+ : {
120
+ summary: previewHarnessText(channel.nextStep || `${channel.label} channel ${channel.setupState}`),
121
+ }),
111
122
  };
112
123
  }
113
124
 
@@ -34,6 +34,11 @@ function readLimit(value: unknown, fallback: number): number {
34
34
  return Math.max(1, Math.min(500, Math.trunc(parsed)));
35
35
  }
36
36
 
37
+ function previewText(value: string, maxLength = 56): string {
38
+ const normalized = value.replace(/\s+/g, ' ').trim();
39
+ return normalized.length <= maxLength ? normalized : `${normalized.slice(0, maxLength - 3).trimEnd()}...`;
40
+ }
41
+
37
42
  function redactCliLookupInput(input: string): string {
38
43
  return input
39
44
  .replace(/(--config=)([^=\s]+)=\S+/g, '$1$2=<redacted>')
@@ -58,6 +63,8 @@ function fallbackCliSummary(command: GoodVibesCliCommand): string {
58
63
  function describeCliCommand(command: GoodVibesCliCommand, lookup?: CliCommandLookup): Record<string, unknown> {
59
64
  const help = describeGoodVibesCommandHelp(command);
60
65
  const tokens = cliCommandTokens(command);
66
+ const policy = describeCliCommandPolicy(command);
67
+ const modelRoute = previewText(policy.preferredModelTool ?? 'agent_harness mode:"cli_command"');
61
68
  return {
62
69
  name: command,
63
70
  tokens,
@@ -69,7 +76,13 @@ function describeCliCommand(command: GoodVibesCliCommand, lookup?: CliCommandLoo
69
76
  aliases: help?.aliases ?? tokens.filter((token) => token !== command),
70
77
  subcommands: help?.subcommands ?? [],
71
78
  examples: help?.examples ?? [],
72
- policy: describeCliCommandPolicy(command),
79
+ modelRoute,
80
+ modelAccess: {
81
+ inspect: `agent_harness mode:"cli_command" cliCommand:"${command}"`,
82
+ executeEquivalent: policy.preferredModelTool ?? 'Use the current conversation or an explicit user-requested shell command.',
83
+ preferred: modelRoute,
84
+ },
85
+ policy,
73
86
  };
74
87
  }
75
88
 
@@ -88,11 +101,16 @@ function cliCommandMatches(command: Record<string, unknown>, query: string): boo
88
101
  }
89
102
 
90
103
  function cliCommandCandidate(command: Record<string, unknown>): Record<string, unknown> {
104
+ const policy = typeof command.policy === 'object' && command.policy !== null
105
+ ? command.policy as { readonly effect?: unknown; readonly preferredModelTool?: unknown }
106
+ : {};
91
107
  return {
92
108
  name: command.name,
93
109
  tokens: command.tokens,
94
110
  invocation: command.invocation,
95
111
  summary: command.summary,
112
+ effect: typeof policy.effect === 'string' ? policy.effect : 'unknown',
113
+ ...(typeof policy.preferredModelTool === 'string' ? { modelRoute: previewText(policy.preferredModelTool) } : {}),
96
114
  };
97
115
  }
98
116
 
@@ -39,9 +39,9 @@ function readStringArray(value: unknown): readonly string[] {
39
39
  return value.map((entry) => typeof entry === 'string' ? entry : String(entry));
40
40
  }
41
41
 
42
- function previewText(value: string, maxLength = 120): string {
42
+ function previewText(value: string, maxLength = 56): string {
43
43
  const normalized = value.replace(/\s+/g, ' ').trim();
44
- return normalized.length <= maxLength ? normalized : `${normalized.slice(0, maxLength - 1).trimEnd()}...`;
44
+ return normalized.length <= maxLength ? normalized : `${normalized.slice(0, maxLength - 3).trimEnd()}...`;
45
45
  }
46
46
 
47
47
  function commandMatches(command: SlashCommand, query: string): boolean {
@@ -57,6 +57,8 @@ function commandMatches(command: SlashCommand, query: string): boolean {
57
57
  }
58
58
 
59
59
  function describeCommand(command: SlashCommand, lookup?: CommandDetailLookup): Record<string, unknown> {
60
+ const policy = describeCommandPolicy(command.name);
61
+ const modelRoute = previewText(policy.preferredModelTool ?? 'agent_harness mode:"command", mode:"run_command"');
60
62
  return {
61
63
  name: command.name,
62
64
  slash: `/${command.name}`,
@@ -64,6 +66,12 @@ function describeCommand(command: SlashCommand, lookup?: CommandDetailLookup): R
64
66
  description: command.description,
65
67
  usage: command.usage ?? '',
66
68
  argsHint: command.argsHint ?? command.usage ?? '',
69
+ modelRoute,
70
+ modelAccess: {
71
+ inspect: `agent_harness mode:"command" commandName:"${command.name}"`,
72
+ run: `agent_harness mode:"run_command" commandName:"${command.name}" confirm:true explicitUserRequest:"..."`,
73
+ preferred: modelRoute,
74
+ },
67
75
  ...(lookup ? {
68
76
  lookup: {
69
77
  source: lookup.source,
@@ -73,16 +81,19 @@ function describeCommand(command: SlashCommand, lookup?: CommandDetailLookup): R
73
81
  resolvedBy: lookup.resolvedBy,
74
82
  },
75
83
  } : {}),
76
- policy: describeCommandPolicy(command.name),
84
+ policy,
77
85
  };
78
86
  }
79
87
 
80
88
  function describeCommandCandidate(command: SlashCommand): Record<string, unknown> {
89
+ const policy = describeCommandPolicy(command.name);
81
90
  return {
82
91
  name: command.name,
83
92
  slash: `/${command.name}`,
84
93
  aliases: command.aliases ?? [],
85
94
  summary: previewText(command.description),
95
+ effect: policy.effect,
96
+ modelRoute: previewText(policy.preferredModelTool ?? 'agent_harness mode:"command", mode:"run_command"'),
86
97
  ...(command.argsHint ? { argsHint: command.argsHint } : {}),
87
98
  };
88
99
  }
@@ -170,7 +181,7 @@ export function resolveHarnessCommandDetail(commandRegistry: CommandRegistry, ar
170
181
 
171
182
  export function listHarnessCommands(commandRegistry: CommandRegistry, args: AgentHarnessCommandCatalogArgs): readonly Record<string, unknown>[] {
172
183
  const query = readString(args.query);
173
- const limit = readLimit(args.limit, 200);
184
+ const limit = readLimit(args.limit, 500);
174
185
  const includeParameters = args.includeParameters === true;
175
186
  return commandRegistry.list()
176
187
  .filter((command) => commandMatches(command, query))
@@ -39,11 +39,11 @@ function connectedHostFindings(
39
39
  });
40
40
  } else if (!runtime.compatible) {
41
41
  findings.push({
42
- id: 'connected-host-version-mismatch',
42
+ id: 'connected-host-incompatible',
43
43
  severity: 'warning',
44
- summary: 'Connected host SDK version does not match Agent.',
45
- cause: `Connected host reports SDK ${runtime.version}; Agent expects ${runtime.expectedVersion}.`,
46
- action: 'Update the owning GoodVibes host so its /status route reports the Agent SDK pin.',
44
+ summary: 'Connected host compatibility does not satisfy Agent readiness.',
45
+ cause: 'Connected host is reachable, but at least one public Agent route is unavailable or incompatible.',
46
+ action: 'Update the owning GoodVibes host so its public Agent routes are compatible.',
47
47
  });
48
48
  }
49
49
 
@@ -88,6 +88,7 @@ export async function connectedHostStatusSummary(
88
88
  return {
89
89
  ownership: 'external-connected-host',
90
90
  readOnly: true,
91
+ modelRoute: 'agent_harness mode:"connected_host_status" or mode:"service_posture"',
91
92
  timeoutMs: CONNECTED_HOST_STATUS_TIMEOUT_MS,
92
93
  lifecycle: 'GoodVibes Agent can inspect connected-host readiness and use public operator routes, but does not start, stop, restart, install, expose, or mutate the host listener.',
93
94
  paths: {
@@ -122,8 +123,6 @@ export async function connectedHostStatusSummary(
122
123
  route: '/status',
123
124
  reachable: runtime.reachable,
124
125
  statusCode: runtime.statusCode,
125
- sdkVersion: runtime.version,
126
- expectedSdkVersion: runtime.expectedVersion,
127
126
  compatible: runtime.compatible,
128
127
  },
129
128
  {
@@ -142,6 +141,8 @@ export async function connectedHostStatusSummary(
142
141
  },
143
142
  ...(options.includeParameters ? { modelAccess: {
144
143
  diagnostics: 'Use mode:"connected_host_status" for live read-only host readiness, mode:"service_posture" for endpoint posture, mode:"service_endpoint" for one endpoint, and mode:"connected_host" for capability and boundary inventory.',
144
+ daemonAliases: 'mode:"daemon_status" is an alias for mode:"connected_host_status"; mode:"daemon" is an alias for mode:"connected_host".',
145
+ lifecycleBlocked: 'Start, stop, restart, install, expose, and listener mutation are not exposed through Agent.',
145
146
  cliMirrors: ['goodvibes-agent status --json', 'goodvibes-agent doctor', 'goodvibes-agent compat'],
146
147
  tuiMirrors: ['Agent Workspace -> Home -> Host compatibility', 'Agent Workspace -> Home -> Doctor diagnostics', 'Agent Workspace -> Home -> Review health'],
147
148
  },
@@ -1,4 +1,5 @@
1
1
  import type { CommandContext } from '../input/command-registry.ts';
2
+ import { previewHarnessText } from './agent-harness-text.ts';
2
3
 
3
4
  export interface AgentHarnessDelegationArgs {
4
5
  readonly delegationRouteId?: unknown;
@@ -113,9 +114,16 @@ function describeCandidate(route: DelegationRoute): Record<string, unknown> {
113
114
  label: route.label,
114
115
  effect: route.effect,
115
116
  confirmationRequired: route.confirmationRequired === true,
117
+ modelRoute: delegationRouteModelRoute(route),
116
118
  };
117
119
  }
118
120
 
121
+ function delegationRouteModelRoute(route: DelegationRoute): string {
122
+ if (route.workspaceActionId) return 'agent_harness mode:"run_workspace_action"';
123
+ if (route.command || route.commandTemplate) return 'agent_harness mode:"run_command"';
124
+ return route.effect === 'main-conversation' ? 'main conversation' : 'agent_harness mode:"delegation_route"';
125
+ }
126
+
119
127
  function describeRoute(
120
128
  context: CommandContext,
121
129
  route: DelegationRoute,
@@ -124,17 +132,20 @@ function describeRoute(
124
132
  return {
125
133
  delegationRouteId: route.id,
126
134
  label: route.label,
127
- detail: route.detail,
135
+ ...(options.includeParameters ? { detail: route.detail } : { summary: previewHarnessText(route.detail) }),
128
136
  effect: route.effect,
129
- ...(route.command ? { command: route.command } : {}),
130
- ...(route.commandTemplate ? { commandTemplate: route.commandTemplate } : {}),
131
- ...(route.workspaceActionId ? { workspaceActionId: route.workspaceActionId } : {}),
132
137
  confirmationRequired: route.confirmationRequired === true,
133
138
  reviewPolicy: route.reviewPolicy ?? 'not-applicable',
139
+ modelRoute: delegationRouteModelRoute(route),
134
140
  runtime: {
135
141
  sessionId: context.session.runtime.sessionId,
136
142
  operatorClientAttached: Boolean(context.clients?.operator),
137
143
  },
144
+ ...(options.includeParameters ? {
145
+ ...(route.command ? { command: route.command } : {}),
146
+ ...(route.commandTemplate ? { commandTemplate: route.commandTemplate } : {}),
147
+ ...(route.workspaceActionId ? { workspaceActionId: route.workspaceActionId } : {}),
148
+ } : {}),
138
149
  ...(options.lookup ? { lookup: options.lookup } : {}),
139
150
  ...(options.includeParameters ? {
140
151
  policy: {
@@ -222,6 +222,8 @@ function bindingCandidate(manager: KeybindingsManager | null, entry: KeybindingE
222
222
  action: entry.action,
223
223
  description: entry.description,
224
224
  labels: entry.combos.map((combo) => formatCombo(manager, combo)),
225
+ modelRoute: keybindingModelRoute(entry.action),
226
+ inspectRoute: `agent_harness mode:"keybinding" actionId:"${entry.action}"`,
225
227
  };
226
228
  }
227
229
 
@@ -246,9 +248,30 @@ function resolveHarnessKeybinding(context: CommandContext, args: HarnessKeybindi
246
248
  return null;
247
249
  }
248
250
 
251
+ function keybindingModelRoute(action: KeyAction): string {
252
+ const route = keybindingOperationRoute(action);
253
+ if (route.preferredMode === 'run_keybinding') return `agent_harness mode:"run_keybinding" actionId:"${action}"`;
254
+ if (route.preferredMode === 'open_ui_surface' && route.surfaceId) return `agent_harness mode:"open_ui_surface" surfaceId:"${route.surfaceId}"`;
255
+ if (route.preferredMode === 'open_ui_surface') return 'agent_harness mode:"open_ui_surface"';
256
+ if (route.preferredMode === 'run_command' && route.command) return `agent_harness mode:"run_command" command:"${route.command}"`;
257
+ if (route.preferredMode === 'run_command') return 'agent_harness mode:"run_command"';
258
+ return 'direct-user-interaction';
259
+ }
260
+
261
+ function keybindingModelAccess(action: KeyAction, operation: KeybindingOperationRoute): Record<string, unknown> {
262
+ return {
263
+ inspect: `agent_harness mode:"keybinding" actionId:"${action}"`,
264
+ run: operation.supported ? `agent_harness mode:"run_keybinding" actionId:"${action}" confirm:true explicitUserRequest:"..."` : 'not exposed',
265
+ set: `agent_harness mode:"set_keybinding" actionId:"${action}" combo:{...} confirm:true explicitUserRequest:"..."`,
266
+ reset: `agent_harness mode:"reset_keybinding" actionId:"${action}" confirm:true explicitUserRequest:"..."`,
267
+ preferred: keybindingModelRoute(action),
268
+ };
269
+ }
270
+
249
271
  function describeBinding(manager: KeybindingsManager | null, action: KeyAction, combos: KeyCombo[], lookup?: KeybindingLookup): Record<string, unknown> {
250
272
  const defaults = DEFAULT_KEYBINDINGS[action];
251
273
  const customized = !combosEqual(combos, defaults);
274
+ const modelOperation = keybindingOperationRoute(action);
252
275
  return {
253
276
  action,
254
277
  description: ACTION_DESCRIPTIONS[action],
@@ -258,7 +281,10 @@ function describeBinding(manager: KeybindingsManager | null, action: KeyAction,
258
281
  defaultBindings: defaults.map((combo) => describeCombo(manager, combo)),
259
282
  customized,
260
283
  source: manager ? (customized ? 'custom' : 'default') : 'default-fallback',
261
- modelOperation: keybindingOperationRoute(action),
284
+ modelRoute: keybindingModelRoute(action),
285
+ inspectRoute: `agent_harness mode:"keybinding" actionId:"${action}"`,
286
+ modelAccess: keybindingModelAccess(action, modelOperation),
287
+ modelOperation,
262
288
  };
263
289
  }
264
290
 
@@ -270,7 +296,7 @@ function keybindingOperationRoute(action: KeyAction): KeybindingOperationRoute {
270
296
  effect: 'shell-action',
271
297
  preferredMode: 'run_keybinding',
272
298
  confirmation: 'agent_harness mode:"run_keybinding" requires confirm:true and explicitUserRequest.',
273
- note: 'Routes the cancel-generation side of this shortcut through commandContext.cancelGeneration. Prompt clearing and double-press exit remain direct user interaction.',
299
+ note: 'Runs the available cancel-generation route. Prompt clearing and double-press exit remain direct user interaction.',
274
300
  };
275
301
  case 'screen-clear':
276
302
  return {
@@ -278,7 +304,7 @@ function keybindingOperationRoute(action: KeyAction): KeybindingOperationRoute {
278
304
  effect: 'shell-action',
279
305
  preferredMode: 'run_keybinding',
280
306
  confirmation: 'agent_harness mode:"run_keybinding" requires confirm:true and explicitUserRequest.',
281
- note: 'Routes through commandContext.clearScreen.',
307
+ note: 'Runs the available clear-screen route.',
282
308
  };
283
309
  case 'panel-picker':
284
310
  return {
@@ -295,7 +321,7 @@ function keybindingOperationRoute(action: KeyAction): KeybindingOperationRoute {
295
321
  effect: 'visible-ui-navigation',
296
322
  preferredMode: 'run_keybinding',
297
323
  confirmation: 'agent_harness mode:"run_keybinding" requires confirm:true and explicitUserRequest.',
298
- note: 'Dismisses the active Agent workspace first. If no Agent workspace is active, closes the active legacy panel if present, then focuses the prompt when the shell bridge exposes focusPrompt.',
324
+ note: 'Dismisses Agent workspace first. If it is not active, closes the active panel and focuses the prompt when focus is available.',
299
325
  };
300
326
  case 'panel-close-all':
301
327
  return {
@@ -303,7 +329,7 @@ function keybindingOperationRoute(action: KeyAction): KeybindingOperationRoute {
303
329
  effect: 'visible-ui-navigation',
304
330
  preferredMode: 'run_keybinding',
305
331
  confirmation: 'agent_harness mode:"run_keybinding" requires confirm:true and explicitUserRequest.',
306
- note: 'Dismisses the active Agent workspace first. If no Agent workspace is active, closes all legacy panels if present, hides the panel manager, then focuses the prompt when the shell bridge exposes focusPrompt.',
332
+ note: 'Dismisses Agent workspace first. If it is not active, closes open panels and focuses the prompt when focus is available.',
307
333
  };
308
334
  case 'history-search':
309
335
  return {
@@ -330,7 +356,7 @@ function keybindingOperationRoute(action: KeyAction): KeybindingOperationRoute {
330
356
  preferredMode: 'run_keybinding',
331
357
  command: '/paste',
332
358
  confirmation: 'agent_harness mode:"run_keybinding" requires confirm:true and explicitUserRequest.',
333
- note: 'Routes through commandContext.pasteFromClipboard and reports whether text, image, or nothing was pasted.',
359
+ note: 'Runs the paste route and reports whether text, image, or nothing was pasted.',
334
360
  };
335
361
  case 'block-copy':
336
362
  case 'bookmark':
@@ -408,13 +434,38 @@ export function totalHarnessShortcuts(context: CommandContext): number {
408
434
  return totalHarnessKeybindings(context) + FIXED_SHORTCUTS.length;
409
435
  }
410
436
 
437
+ function fixedShortcutModelRoute(shortcut: Record<string, string>): string {
438
+ if (shortcut.key.includes('/shortcuts')) return 'agent_harness mode:"shortcuts"';
439
+ if (shortcut.key.includes('/keybindings')) return 'agent_harness mode:"keybindings"';
440
+ if (shortcut.key.includes('?') || shortcut.key.includes('F1')) return 'agent_harness mode:"open_ui_surface" surfaceId:"help-overlay"';
441
+ if (shortcut.key.includes('F2')) return 'agent_harness mode:"open_ui_surface" surfaceId:"process-monitor"';
442
+ if (shortcut.key.includes('Esc')) return 'direct-user-interaction';
443
+ if (shortcut.key.includes('Tab')) return 'agent_harness mode:"commands"';
444
+ if (shortcut.key.includes('Enter')) return 'main conversation';
445
+ return 'direct-user-interaction';
446
+ }
447
+
448
+ function describeFixedShortcut(shortcut: Record<string, string>): Record<string, unknown> {
449
+ return {
450
+ ...shortcut,
451
+ source: 'fixed',
452
+ userEditable: false,
453
+ modelRoute: fixedShortcutModelRoute(shortcut),
454
+ modelAccess: {
455
+ inspectShortcuts: 'agent_harness mode:"shortcuts"',
456
+ inspectKeybindings: 'agent_harness mode:"keybindings"',
457
+ preferred: fixedShortcutModelRoute(shortcut),
458
+ },
459
+ };
460
+ }
461
+
411
462
  export function listHarnessShortcuts(context: CommandContext, args: HarnessKeybindingArgs): Record<string, unknown> {
412
463
  const keybindings = listHarnessKeybindings(context, args);
413
464
  const query = readString(args.query).toLowerCase();
414
465
  const fixed = FIXED_SHORTCUTS
415
466
  .filter((shortcut) => !query || `${shortcut.key}\n${shortcut.description}`.toLowerCase().includes(query))
416
467
  .slice(0, readLimit(args.limit, 200))
417
- .map((shortcut) => ({ ...shortcut, source: 'fixed', userEditable: false }));
468
+ .map(describeFixedShortcut);
418
469
  const degraded = keybindings.status === 'degraded';
419
470
  return {
420
471
  status: degraded ? 'degraded' : 'available',
@@ -496,11 +547,11 @@ export function runHarnessKeybinding(context: CommandContext, args: HarnessKeybi
496
547
 
497
548
  switch (resolved.action) {
498
549
  case 'clear-cancel':
499
- if (!context.cancelGeneration) return runUnavailable(resolved.action, route, 'commandContext.cancelGeneration is unavailable.');
550
+ if (!context.cancelGeneration) return runUnavailable(resolved.action, route, 'Cancel-generation route is unavailable.');
500
551
  context.cancelGeneration();
501
552
  return { status: 'executed', action: resolved.action, effect: 'cancel-generation', keybinding: descriptor };
502
553
  case 'screen-clear':
503
- if (!context.clearScreen) return runUnavailable(resolved.action, route, 'commandContext.clearScreen is unavailable.');
554
+ if (!context.clearScreen) return runUnavailable(resolved.action, route, 'Clear-screen route is unavailable.');
504
555
  context.clearScreen();
505
556
  return { status: 'executed', action: resolved.action, effect: 'screen-clear', keybinding: descriptor };
506
557
  case 'panel-picker':
@@ -512,7 +563,7 @@ export function runHarnessKeybinding(context: CommandContext, args: HarnessKeybi
512
563
  context.openAgentWorkspace('home');
513
564
  return { status: 'executed', action: resolved.action, effect: 'agent-workspace-opened', route: 'openAgentWorkspace', categoryId: 'home', keybinding: descriptor };
514
565
  }
515
- return runUnavailable(resolved.action, route, 'No panel picker or Agent workspace opener is available.');
566
+ return runUnavailable(resolved.action, route, 'No panel picker or Agent workspace route is available.');
516
567
  case 'panel-close': {
517
568
  const dismissedAgentWorkspace = context.dismissAgentWorkspace?.() ?? false;
518
569
  if (dismissedAgentWorkspace) {
@@ -526,7 +577,7 @@ export function runHarnessKeybinding(context: CommandContext, args: HarnessKeybi
526
577
  }
527
578
  const active = context.workspace.panelManager?.getActivePanel() ?? null;
528
579
  if (active) context.workspace.panelManager?.close(active.id);
529
- if (!active && !context.focusPrompt) return runUnavailable(resolved.action, route, 'No active Agent workspace, active legacy panel, or prompt focus route is available.');
580
+ if (!active && !context.focusPrompt) return runUnavailable(resolved.action, route, 'No active Agent workspace, active panel, or prompt focus route is available.');
530
581
  if (context.focusPrompt) context.focusPrompt();
531
582
  context.renderRequest();
532
583
  return {
@@ -552,7 +603,7 @@ export function runHarnessKeybinding(context: CommandContext, args: HarnessKeybi
552
603
  const openPanels = managerPanel?.getAllOpen() ?? [];
553
604
  for (const panel of openPanels) managerPanel?.close(panel.id);
554
605
  managerPanel?.hide();
555
- if (openPanels.length === 0 && !context.focusPrompt) return runUnavailable(resolved.action, route, 'No active Agent workspace, open legacy panels, or prompt focus route is available.');
606
+ if (openPanels.length === 0 && !context.focusPrompt) return runUnavailable(resolved.action, route, 'No active Agent workspace, open panels, or prompt focus route is available.');
556
607
  if (context.focusPrompt) context.focusPrompt();
557
608
  context.renderRequest();
558
609
  return {
@@ -564,26 +615,26 @@ export function runHarnessKeybinding(context: CommandContext, args: HarnessKeybi
564
615
  };
565
616
  }
566
617
  case 'history-search': {
567
- if (!context.openPromptHistorySearch) return runUnavailable(resolved.action, route, 'commandContext.openPromptHistorySearch is unavailable.');
618
+ if (!context.openPromptHistorySearch) return runUnavailable(resolved.action, route, 'Prompt history search route is unavailable.');
568
619
  const query = readString(args.value);
569
620
  context.openPromptHistorySearch(query || undefined);
570
621
  return { status: 'executed', action: resolved.action, effect: 'prompt-history-search-opened', query, keybinding: descriptor };
571
622
  }
572
623
  case 'search': {
573
- if (!context.openConversationSearch) return runUnavailable(resolved.action, route, 'commandContext.openConversationSearch is unavailable.');
624
+ if (!context.openConversationSearch) return runUnavailable(resolved.action, route, 'Conversation search route is unavailable.');
574
625
  const query = readString(args.value);
575
626
  context.openConversationSearch(query || undefined);
576
627
  return { status: 'executed', action: resolved.action, effect: 'conversation-search-opened', query, keybinding: descriptor };
577
628
  }
578
629
  case 'paste': {
579
- if (!context.pasteFromClipboard) return runUnavailable(resolved.action, route, 'commandContext.pasteFromClipboard is unavailable.');
630
+ if (!context.pasteFromClipboard) return runUnavailable(resolved.action, route, 'Paste route is unavailable.');
580
631
  const pasted = context.pasteFromClipboard();
581
632
  return { status: 'executed', action: resolved.action, effect: 'paste-from-clipboard', pasted, keybinding: descriptor };
582
633
  }
583
634
  case 'block-copy':
584
635
  case 'bookmark':
585
636
  case 'block-save': {
586
- if (!context.openBlockActions) return runUnavailable(resolved.action, route, 'commandContext.openBlockActions is unavailable.');
637
+ if (!context.openBlockActions) return runUnavailable(resolved.action, route, 'Block action surface route is unavailable.');
587
638
  const opened = context.openBlockActions();
588
639
  return opened
589
640
  ? {
@@ -1,4 +1,5 @@
1
1
  import type { CommandContext } from '../input/command-registry.ts';
2
+ import { previewHarnessText } from './agent-harness-text.ts';
2
3
 
3
4
  export interface AgentHarnessMcpArgs {
4
5
  readonly mcpServerId?: unknown;
@@ -76,9 +77,14 @@ function describeCandidate(server: McpServerRecord): Record<string, unknown> {
76
77
  trustMode: server.trustMode,
77
78
  role: server.role,
78
79
  schemaFreshness: server.schemaFreshness,
80
+ modelRoute: mcpServerModelRoute(),
79
81
  };
80
82
  }
81
83
 
84
+ function mcpServerModelRoute(): string {
85
+ return 'agent_harness mode:"mcp_server" or mode:"run_command"';
86
+ }
87
+
82
88
  function toolsByServer(tools: readonly McpToolRecord[]): ReadonlyMap<string, readonly McpToolRecord[]> {
83
89
  const grouped = new Map<string, McpToolRecord[]>();
84
90
  for (const tool of tools) {
@@ -108,6 +114,7 @@ function describeServer(
108
114
  allowedHostCount: server.allowedHosts.length,
109
115
  ...(server.quarantineReason ? { quarantineReason: server.quarantineReason } : {}),
110
116
  ...(server.quarantineDetail ? { quarantineDetail: server.quarantineDetail } : {}),
117
+ modelRoute: mcpServerModelRoute(),
111
118
  ...(options.lookup ? { lookup: options.lookup } : {}),
112
119
  ...(options.includeParameters ? {
113
120
  tools: tools.map((tool) => ({
@@ -115,8 +122,6 @@ function describeServer(
115
122
  ...(tool.description ? { description: tool.description } : {}),
116
123
  })),
117
124
  toolCount: tools.length,
118
- } : { toolCount: tools.length }),
119
- ...(options.includeParameters ? {
120
125
  policy: {
121
126
  effect: 'read-only',
122
127
  values: 'Server posture returns trust, role, connection, quarantine, and tool metadata; env values and secret config values are never returned.',
@@ -145,7 +150,10 @@ function describeServer(
145
150
  `/mcp remove ${server.name} --yes`,
146
151
  ],
147
152
  },
148
- } : {}),
153
+ } : {
154
+ toolCount: tools.length,
155
+ summary: previewHarnessText(`${server.connected ? 'connected' : 'disconnected'} ${server.trustMode} ${server.schemaFreshness}`),
156
+ }),
149
157
  };
150
158
  }
151
159
 
@@ -1,6 +1,7 @@
1
1
  import { buildAgentWorkspaceVoiceMediaReadiness } from '../input/agent-workspace-voice-media.ts';
2
2
  import type { AgentWorkspaceVoiceMediaProviderStatus } from '../input/agent-workspace-voice-media.ts';
3
3
  import type { CommandContext } from '../input/command-registry.ts';
4
+ import { previewHarnessText } from './agent-harness-text.ts';
4
5
 
5
6
  export interface AgentHarnessMediaArgs {
6
7
  readonly mediaProviderId?: unknown;
@@ -77,9 +78,14 @@ function describeProviderCandidate(provider: AgentWorkspaceVoiceMediaProviderSta
77
78
  label: provider.label,
78
79
  setupState: provider.setupState,
79
80
  selected: provider.selected,
81
+ modelRoute: mediaProviderModelRoute(),
80
82
  };
81
83
  }
82
84
 
85
+ function mediaProviderModelRoute(): string {
86
+ return 'agent_media_generate or agent_harness mode:"media_provider"';
87
+ }
88
+
83
89
  function statusMap(statuses: readonly RuntimeProviderStatus[]): ReadonlyMap<string, RuntimeProviderStatus> {
84
90
  return new Map(statuses.map((status) => [status.id, status]));
85
91
  }
@@ -103,27 +109,32 @@ function describeProvider(
103
109
  configuredSecretKeyNames: provider.configuredSecretKeys,
104
110
  missingSecretKeyNames: provider.missingSecretKeyOptions,
105
111
  nextStep: provider.nextStep,
112
+ modelRoute: mediaProviderModelRoute(),
106
113
  ...(runtimeStatus ? {
107
114
  runtimeStatus: {
108
115
  state: runtimeStatus.state,
109
116
  configured: runtimeStatus.configured,
110
- ...(runtimeStatus.detail ? { detail: runtimeStatus.detail } : {}),
117
+ ...(options.includeParameters && runtimeStatus.detail ? { detail: runtimeStatus.detail } : {}),
111
118
  },
112
119
  } : {}),
113
120
  ...(options.lookup ? { lookup: options.lookup } : {}),
114
- ...(options.includeParameters ? {
115
- modelRoutes: {
116
- inspectPosture: 'agent_harness mode:"media_posture"',
117
- inspectProvider: 'agent_harness mode:"media_provider"',
118
- generateMedia: 'agent_media_generate with confirm:true and explicitUserRequest',
119
- ttsSettings: 'agent_harness settings/get_setting/set_setting for tts.provider, tts.voice, tts.llmProvider, and tts.llmModel',
120
- },
121
- policy: {
122
- effect: 'read-only',
123
- values: 'Provider posture returns capability, setup, selected, health, and safe environment key names only; secret values and media payloads are never returned.',
124
- mutation: 'Media generation, voice enable/disable, TTS setting changes, and bundle export stay explicit confirmation-gated tool, setting, workspace, or slash-command flows.',
125
- },
126
- } : {}),
121
+ ...(options.includeParameters
122
+ ? {
123
+ modelRoutes: {
124
+ inspectPosture: 'agent_harness mode:"media_posture"',
125
+ inspectProvider: 'agent_harness mode:"media_provider"',
126
+ generateMedia: 'agent_media_generate with confirm:true and explicitUserRequest',
127
+ ttsSettings: 'agent_harness mode:"settings", mode:"get_setting", mode:"set_setting" for tts.provider, tts.voice, tts.llmProvider, and tts.llmModel',
128
+ },
129
+ policy: {
130
+ effect: 'read-only',
131
+ values: 'Provider posture returns capability, setup, selected, health, and safe environment key names only; secret values and media payloads are never returned.',
132
+ mutation: 'Media generation, voice enable/disable, TTS setting changes, and bundle export stay explicit confirmation-gated tool, setting, workspace, or slash-command flows.',
133
+ },
134
+ }
135
+ : {
136
+ summary: previewHarnessText(provider.nextStep || `${provider.domain} provider ${provider.setupState}`),
137
+ }),
127
138
  };
128
139
  }
129
140