@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.
- package/CHANGELOG.md +28 -13
- package/README.md +11 -8
- package/dist/package/main.js +1592 -921
- package/docs/README.md +6 -5
- package/docs/channels-remote-and-api.md +3 -3
- package/docs/connected-host.md +3 -3
- package/docs/getting-started.md +20 -12
- package/docs/knowledge-artifacts-and-multimodal.md +9 -4
- package/docs/project-planning.md +3 -3
- package/docs/providers-and-routing.md +2 -2
- package/docs/release-and-publishing.md +8 -8
- package/docs/tools-and-commands.md +34 -17
- package/docs/voice-and-live-tts.md +1 -1
- package/package.json +1 -1
- package/release/live-verification/live-verification.json +15 -15
- package/release/live-verification/live-verification.md +19 -21
- package/release/release-notes.md +6 -6
- package/release/release-readiness.json +64 -64
- package/src/agent/harness-control.ts +35 -9
- package/src/agent/reminder-schedule-format.ts +2 -5
- package/src/agent/reminder-schedule.ts +4 -11
- package/src/agent/routine-schedule-format.ts +3 -6
- package/src/agent/routine-schedule-promotion.ts +5 -14
- package/src/agent/routine-schedule-receipts.ts +4 -9
- package/src/cli/agent-knowledge-command.ts +6 -11
- package/src/cli/agent-knowledge-format.ts +4 -9
- package/src/cli/agent-knowledge-runtime.ts +7 -14
- package/src/cli/external-runtime.ts +3 -46
- package/src/cli/help.ts +2 -2
- package/src/cli/status.ts +6 -7
- package/src/input/agent-workspace-categories.ts +23 -23
- package/src/input/agent-workspace-channels.ts +3 -3
- package/src/input/agent-workspace-operations-command-editors.ts +1 -1
- package/src/input/command-registry.ts +1 -1
- package/src/input/commands/compat-runtime.ts +1 -1
- package/src/input/commands/planning-runtime.ts +1 -1
- package/src/input/commands/runtime-services.ts +1 -1
- package/src/input/mcp-workspace.ts +1 -1
- package/src/input/submission-router.ts +1 -1
- package/src/panels/builtin/shared.ts +1 -1
- package/src/panels/project-planning-panel.ts +4 -4
- package/src/panels/qr-panel.ts +2 -2
- package/src/renderer/conversation-overlays.ts +1 -0
- package/src/runtime/bootstrap-command-parts.ts +1 -1
- package/src/runtime/bootstrap-core.ts +3 -1
- package/src/runtime/bootstrap-hook-bridge.ts +1 -1
- package/src/runtime/bootstrap-shell.ts +1 -1
- package/src/runtime/bootstrap.ts +7 -4
- package/src/runtime/context.ts +2 -2
- package/src/runtime/index.ts +88 -5
- package/src/runtime/store/state.ts +1 -1
- package/src/tools/agent-channel-send-tool.ts +1 -1
- package/src/tools/agent-harness-channel-metadata.ts +36 -25
- package/src/tools/agent-harness-cli-metadata.ts +19 -1
- package/src/tools/agent-harness-command-catalog.ts +15 -4
- package/src/tools/agent-harness-connected-host-status.ts +7 -6
- package/src/tools/agent-harness-delegation-posture.ts +15 -4
- package/src/tools/agent-harness-keybinding-metadata.ts +67 -16
- package/src/tools/agent-harness-mcp-metadata.ts +11 -3
- package/src/tools/agent-harness-media-posture.ts +25 -14
- package/src/tools/agent-harness-metadata.ts +140 -37
- package/src/tools/agent-harness-mode-catalog.ts +258 -0
- package/src/tools/agent-harness-model-routing.ts +18 -3
- package/src/tools/agent-harness-model-tool-catalog.ts +85 -7
- package/src/tools/agent-harness-notification-metadata.ts +30 -19
- package/src/tools/agent-harness-operator-methods.ts +15 -10
- package/src/tools/agent-harness-pairing-posture.ts +17 -5
- package/src/tools/agent-harness-panel-metadata.ts +10 -4
- package/src/tools/agent-harness-provider-account-metadata.ts +46 -35
- package/src/tools/agent-harness-release-evidence.ts +31 -3
- package/src/tools/agent-harness-release-readiness.ts +30 -0
- package/src/tools/agent-harness-security-posture.ts +18 -4
- package/src/tools/agent-harness-service-posture.ts +9 -2
- package/src/tools/agent-harness-session-metadata.ts +24 -8
- package/src/tools/agent-harness-setup-posture.ts +32 -21
- package/src/tools/agent-harness-text.ts +6 -0
- package/src/tools/agent-harness-tool-schema.ts +1 -1
- package/src/tools/agent-harness-tool.ts +38 -36
- package/src/tools/agent-harness-ui-surface-metadata.ts +105 -101
- package/src/tools/agent-harness-workspace-actions.ts +64 -3
- package/src/tools/agent-knowledge-ingest-tool.ts +1 -1
- package/src/tools/agent-knowledge-tool.ts +1 -1
- package/src/tools/agent-media-generate-tool.ts +2 -2
- package/src/tools/agent-notify-tool.ts +1 -1
- package/src/tools/agent-reminder-schedule-tool.ts +1 -1
- package/src/tools/tool-definition-compaction.ts +76 -0
- 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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
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 =
|
|
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 -
|
|
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
|
|
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,
|
|
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-
|
|
42
|
+
id: 'connected-host-incompatible',
|
|
43
43
|
severity: 'warning',
|
|
44
|
-
summary: 'Connected host
|
|
45
|
-
cause:
|
|
46
|
-
action: 'Update the owning GoodVibes host so its
|
|
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
|
-
|
|
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: '
|
|
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: '
|
|
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
|
|
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
|
|
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: '
|
|
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(
|
|
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, '
|
|
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, '
|
|
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
|
|
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
|
|
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
|
|
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, '
|
|
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, '
|
|
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, '
|
|
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, '
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
|