@pellux/goodvibes-agent 1.0.31 → 1.0.34
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 +59 -2
- package/README.md +72 -58
- package/dist/package/main.js +7572 -2430
- package/docs/README.md +25 -21
- package/docs/channels-remote-and-api.md +6 -2
- package/docs/connected-host.md +27 -6
- package/docs/getting-started.md +87 -66
- package/docs/knowledge-artifacts-and-multimodal.md +16 -4
- package/docs/project-planning.md +2 -2
- package/docs/providers-and-routing.md +3 -2
- package/docs/release-and-publishing.md +15 -11
- package/docs/tools-and-commands.md +150 -128
- package/docs/voice-and-live-tts.md +1 -1
- package/package.json +8 -3
- package/release/live-verification/live-verification.json +148 -0
- package/release/live-verification/live-verification.md +187 -0
- package/release/performance-snapshot.json +57 -0
- package/release/release-notes.md +19 -0
- package/release/release-readiness.json +581 -0
- package/src/agent/harness-control.ts +42 -3
- package/src/cli/agent-knowledge-command.ts +5 -5
- package/src/cli/agent-knowledge-format.ts +11 -0
- package/src/cli/agent-knowledge-runtime.ts +92 -13
- package/src/cli/bundle-command.ts +5 -4
- package/src/cli/entrypoint.ts +5 -2
- package/src/cli/external-runtime.ts +2 -15
- package/src/cli/management.ts +4 -3
- package/src/input/commands/guidance-runtime.ts +1 -1
- package/src/input/commands/knowledge.ts +2 -2
- package/src/runtime/bootstrap.ts +10 -18
- package/src/runtime/connected-host-auth.ts +16 -0
- package/src/tools/agent-analysis-registry-policy.ts +2 -9
- package/src/tools/agent-channel-send-tool.ts +3 -9
- package/src/tools/agent-context-policy.ts +1 -5
- package/src/tools/agent-find-policy.ts +1 -4
- package/src/tools/agent-harness-channel-metadata.ts +177 -0
- package/src/tools/agent-harness-cli-metadata.ts +4 -1
- package/src/tools/agent-harness-command-catalog.ts +10 -3
- package/src/tools/agent-harness-connected-host-status.ts +9 -3
- package/src/tools/agent-harness-delegation-posture.ts +216 -0
- package/src/tools/agent-harness-keybinding-metadata.ts +57 -22
- package/src/tools/agent-harness-mcp-metadata.ts +248 -0
- package/src/tools/agent-harness-media-posture.ts +282 -0
- package/src/tools/agent-harness-metadata.ts +44 -9
- package/src/tools/agent-harness-model-routing.ts +501 -0
- package/src/tools/agent-harness-model-tool-catalog.ts +7 -2
- package/src/tools/agent-harness-notification-metadata.ts +217 -0
- package/src/tools/agent-harness-operator-methods.ts +285 -0
- package/src/tools/agent-harness-pairing-posture.ts +265 -0
- package/src/tools/agent-harness-panel-metadata.ts +26 -12
- package/src/tools/agent-harness-provider-account-metadata.ts +205 -0
- package/src/tools/agent-harness-release-evidence.ts +364 -0
- package/src/tools/agent-harness-release-readiness.ts +298 -0
- package/src/tools/agent-harness-security-posture.ts +648 -0
- package/src/tools/agent-harness-service-posture.ts +207 -0
- package/src/tools/agent-harness-session-metadata.ts +284 -0
- package/src/tools/agent-harness-setup-posture.ts +295 -0
- package/src/tools/agent-harness-tool-schema.ts +104 -27
- package/src/tools/agent-harness-tool.ts +251 -235
- package/src/tools/agent-harness-ui-surface-metadata.ts +20 -12
- package/src/tools/agent-harness-workspace-actions.ts +260 -0
- package/src/tools/agent-knowledge-ingest-tool.ts +4 -10
- package/src/tools/agent-knowledge-tool.ts +120 -25
- package/src/tools/agent-local-registry-tool.ts +3 -7
- package/src/tools/agent-media-generate-tool.ts +2 -8
- package/src/tools/agent-notify-tool.ts +3 -8
- package/src/tools/agent-operator-action-tool.ts +4 -10
- package/src/tools/agent-operator-briefing-tool.ts +1 -6
- package/src/tools/agent-read-policy.ts +1 -4
- package/src/tools/agent-reminder-schedule-tool.ts +4 -9
- package/src/tools/agent-tool-policy-guard.ts +15 -51
- package/src/tools/agent-web-search-policy.ts +1 -4
- package/src/tools/agent-work-plan-tool.ts +1 -6
- package/src/version.ts +2 -2
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { networkInterfaces } from 'node:os';
|
|
2
|
+
import type { CommandContext } from '../input/command-registry.ts';
|
|
3
|
+
import { GOODVIBES_AGENT_PAIRING_SURFACE } from '../config/surface.ts';
|
|
4
|
+
import { resolveRuntimeEndpointBinding } from '../cli/endpoints.ts';
|
|
5
|
+
import { connectedHostOperatorTokenFingerprint, readConnectedHostOperatorToken } from '../runtime/connected-host-auth.ts';
|
|
6
|
+
import { requirePlatform, requireShellPaths } from '../input/commands/runtime-services.ts';
|
|
7
|
+
|
|
8
|
+
export interface AgentHarnessPairingArgs {
|
|
9
|
+
readonly pairingRouteId?: unknown;
|
|
10
|
+
readonly target?: unknown;
|
|
11
|
+
readonly query?: unknown;
|
|
12
|
+
readonly includeParameters?: unknown;
|
|
13
|
+
readonly limit?: unknown;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type PairingResolution =
|
|
17
|
+
| { readonly status: 'found'; readonly route: Record<string, unknown> }
|
|
18
|
+
| { readonly status: 'ambiguous'; readonly input: string; readonly candidates: readonly Record<string, unknown>[] }
|
|
19
|
+
| { readonly status: 'missing_lookup'; readonly usage: string };
|
|
20
|
+
|
|
21
|
+
type PairingLookupSource = 'pairingRouteId' | 'target' | 'query';
|
|
22
|
+
|
|
23
|
+
interface PairingRoute {
|
|
24
|
+
readonly id: string;
|
|
25
|
+
readonly label: string;
|
|
26
|
+
readonly detail: string;
|
|
27
|
+
readonly effect: 'read-only' | 'visible-navigation' | 'external-network' | 'confirmation-gated-secret-display';
|
|
28
|
+
readonly command?: string;
|
|
29
|
+
readonly harnessRoute?: string;
|
|
30
|
+
readonly capabilityIds?: readonly string[];
|
|
31
|
+
readonly requiresConfirmation?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function readString(value: unknown): string {
|
|
35
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function readLimit(value: unknown, fallback: number): number {
|
|
39
|
+
const parsed = typeof value === 'string' && value.trim() ? Number(value) : value;
|
|
40
|
+
if (typeof parsed !== 'number' || !Number.isFinite(parsed)) return fallback;
|
|
41
|
+
return Math.max(1, Math.min(500, Math.trunc(parsed)));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function localNetworkIp(): string {
|
|
45
|
+
try {
|
|
46
|
+
const nets = networkInterfaces();
|
|
47
|
+
for (const name of Object.keys(nets)) {
|
|
48
|
+
for (const netInfo of nets[name] ?? []) {
|
|
49
|
+
if (netInfo.family === 'IPv4' && !netInfo.internal) return netInfo.address;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
return '127.0.0.1';
|
|
54
|
+
}
|
|
55
|
+
return '127.0.0.1';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function urlHostForBindHost(host: string): string {
|
|
59
|
+
if (host === '0.0.0.0' || host === '::') return localNetworkIp();
|
|
60
|
+
return host || '127.0.0.1';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function lookupFromArgs(args: AgentHarnessPairingArgs): { readonly source: PairingLookupSource; readonly input: string } | null {
|
|
64
|
+
const pairingRouteId = readString(args.pairingRouteId);
|
|
65
|
+
if (pairingRouteId) return { source: 'pairingRouteId', input: pairingRouteId };
|
|
66
|
+
const target = readString(args.target);
|
|
67
|
+
if (target) return { source: 'target', input: target };
|
|
68
|
+
const query = readString(args.query);
|
|
69
|
+
return query ? { source: 'query', input: query } : null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function pairingRoutes(): readonly PairingRoute[] {
|
|
73
|
+
return [
|
|
74
|
+
{
|
|
75
|
+
id: 'qr-pairing',
|
|
76
|
+
label: 'QR pairing',
|
|
77
|
+
detail: 'Visible companion pairing route that prints QR setup details in the Agent TUI without printing the raw token.',
|
|
78
|
+
effect: 'external-network',
|
|
79
|
+
command: '/pair',
|
|
80
|
+
harnessRoute: 'agent_harness mode:"run_command" command:"/pair" confirm:true explicitUserRequest:"..."',
|
|
81
|
+
requiresConfirmation: true,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: 'manual-token-display',
|
|
85
|
+
label: 'Manual token display',
|
|
86
|
+
detail: 'Explicitly confirmed fallback route that prints the raw companion token only when the user asks for manual setup.',
|
|
87
|
+
effect: 'confirmation-gated-secret-display',
|
|
88
|
+
command: '/pair --show-token --yes',
|
|
89
|
+
harnessRoute: 'agent_harness mode:"run_command" command:"/pair --show-token --yes" confirm:true explicitUserRequest:"..."',
|
|
90
|
+
requiresConfirmation: true,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'pairing-ui',
|
|
94
|
+
label: 'Pairing workspace route',
|
|
95
|
+
detail: 'Visible Agent workspace route for companion pairing and channel setup.',
|
|
96
|
+
effect: 'visible-navigation',
|
|
97
|
+
command: '/agent channels',
|
|
98
|
+
harnessRoute: 'agent_harness mode:"workspace_action" target:"pair"',
|
|
99
|
+
capabilityIds: ['companion-pairing', 'mobile-command-routing'],
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 'connected-host-status',
|
|
103
|
+
label: 'Connected host live posture',
|
|
104
|
+
detail: 'Read-only reachability, token posture, SDK compatibility, and route readiness used before companion setup.',
|
|
105
|
+
effect: 'read-only',
|
|
106
|
+
harnessRoute: 'agent_harness mode:"connected_host_status"',
|
|
107
|
+
capabilityIds: ['connected-host-status'],
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: 'companion-capabilities',
|
|
111
|
+
label: 'Companion capability map',
|
|
112
|
+
detail: 'Allowed and blocked companion route families for pairing, shared sessions, tasks, approvals, provider/model changes, attachments, and mobile command surfaces.',
|
|
113
|
+
effect: 'read-only',
|
|
114
|
+
harnessRoute: 'agent_harness mode:"connected_host"',
|
|
115
|
+
capabilityIds: [
|
|
116
|
+
'companion-pairing',
|
|
117
|
+
'shared-session',
|
|
118
|
+
'task-management',
|
|
119
|
+
'approval-actions',
|
|
120
|
+
'provider-model-routing',
|
|
121
|
+
'attachment-upload',
|
|
122
|
+
'mobile-command-routing',
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
id: 'channels-readiness',
|
|
127
|
+
label: 'Channel readiness',
|
|
128
|
+
detail: 'Read-only channel setup and delivery posture used after pairing when messages or reminders need an explicit delivery target.',
|
|
129
|
+
effect: 'read-only',
|
|
130
|
+
command: '/channels',
|
|
131
|
+
harnessRoute: 'agent_harness mode:"channels"',
|
|
132
|
+
capabilityIds: ['channels', 'notifications'],
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function routeSearchText(route: PairingRoute): string {
|
|
138
|
+
return [
|
|
139
|
+
route.id,
|
|
140
|
+
route.label,
|
|
141
|
+
route.detail,
|
|
142
|
+
route.effect,
|
|
143
|
+
route.command ?? '',
|
|
144
|
+
route.harnessRoute ?? '',
|
|
145
|
+
...(route.capabilityIds ?? []),
|
|
146
|
+
].join('\n').toLowerCase();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function describeCandidate(route: PairingRoute): Record<string, unknown> {
|
|
150
|
+
return {
|
|
151
|
+
pairingRouteId: route.id,
|
|
152
|
+
label: route.label,
|
|
153
|
+
effect: route.effect,
|
|
154
|
+
requiresConfirmation: route.requiresConfirmation === true,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function describeRoute(route: PairingRoute, options: { readonly includeParameters?: boolean; readonly lookup?: Record<string, unknown> } = {}): Record<string, unknown> {
|
|
159
|
+
return {
|
|
160
|
+
pairingRouteId: route.id,
|
|
161
|
+
label: route.label,
|
|
162
|
+
detail: route.detail,
|
|
163
|
+
effect: route.effect,
|
|
164
|
+
...(route.command ? { command: route.command } : {}),
|
|
165
|
+
...(route.harnessRoute ? { harnessRoute: route.harnessRoute } : {}),
|
|
166
|
+
...(route.capabilityIds ? { capabilityIds: route.capabilityIds } : {}),
|
|
167
|
+
requiresConfirmation: route.requiresConfirmation === true,
|
|
168
|
+
...(options.lookup ? { lookup: options.lookup } : {}),
|
|
169
|
+
...(options.includeParameters ? {
|
|
170
|
+
policy: {
|
|
171
|
+
effect: route.effect,
|
|
172
|
+
values: 'Pairing posture returns endpoint binding and token fingerprint only; raw companion tokens and QR payloads are never returned by this read-only mode.',
|
|
173
|
+
mutation: 'Pairing display, manual token display, companion connection, channel sends, provider/model changes, approval actions, and attachment flows stay explicit visible user flows.',
|
|
174
|
+
},
|
|
175
|
+
modelAccess: {
|
|
176
|
+
inspectPairing: 'agent_harness mode:"pairing_posture"',
|
|
177
|
+
inspectRoute: 'agent_harness mode:"pairing_route"',
|
|
178
|
+
connectedHostStatus: 'agent_harness mode:"connected_host_status"',
|
|
179
|
+
connectedHostCapabilities: 'agent_harness mode:"connected_host"',
|
|
180
|
+
channels: 'agent_harness mode:"channels"',
|
|
181
|
+
},
|
|
182
|
+
} : {}),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function pairingState(context: CommandContext): Record<string, unknown> {
|
|
187
|
+
const shellPaths = requireShellPaths(context);
|
|
188
|
+
const configManager = requirePlatform(context).configManager;
|
|
189
|
+
const tokenRecord = readConnectedHostOperatorToken(shellPaths.homeDirectory);
|
|
190
|
+
const binding = resolveRuntimeEndpointBinding(configManager, 'controlPlane');
|
|
191
|
+
const host = urlHostForBindHost(binding.host);
|
|
192
|
+
return {
|
|
193
|
+
surface: GOODVIBES_AGENT_PAIRING_SURFACE,
|
|
194
|
+
endpoint: {
|
|
195
|
+
endpointId: 'controlPlane',
|
|
196
|
+
bindHost: binding.host,
|
|
197
|
+
advertisedHost: host,
|
|
198
|
+
port: binding.port,
|
|
199
|
+
url: `http://${host}:${binding.port}`,
|
|
200
|
+
},
|
|
201
|
+
token: {
|
|
202
|
+
present: Boolean(tokenRecord.token),
|
|
203
|
+
path: tokenRecord.path,
|
|
204
|
+
fingerprint: tokenRecord.token ? `sha256:${connectedHostOperatorTokenFingerprint(tokenRecord.token)}` : null,
|
|
205
|
+
rawValueReturned: false,
|
|
206
|
+
},
|
|
207
|
+
companionPayloadReturned: false,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function pairingPostureCatalogStatus(context: CommandContext): Record<string, unknown> {
|
|
212
|
+
const state = pairingState(context);
|
|
213
|
+
return {
|
|
214
|
+
modes: ['pairing_posture', 'pairing_route'],
|
|
215
|
+
routes: pairingRoutes().length,
|
|
216
|
+
tokenPresent: (state.token as { readonly present?: boolean }).present === true,
|
|
217
|
+
readOnly: true,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function pairingPostureSummary(context: CommandContext, args: AgentHarnessPairingArgs): Record<string, unknown> {
|
|
222
|
+
const query = readString(args.query).toLowerCase();
|
|
223
|
+
const includeParameters = args.includeParameters === true;
|
|
224
|
+
const routes = pairingRoutes();
|
|
225
|
+
const filtered = routes
|
|
226
|
+
.filter((route) => !query || routeSearchText(route).includes(query))
|
|
227
|
+
.slice(0, readLimit(args.limit, 100));
|
|
228
|
+
return {
|
|
229
|
+
status: 'available',
|
|
230
|
+
pairing: pairingState(context),
|
|
231
|
+
routes: filtered.map((route) => describeRoute(route, { includeParameters })),
|
|
232
|
+
returned: filtered.length,
|
|
233
|
+
total: routes.length,
|
|
234
|
+
policy: 'Read-only pairing posture. QR display, manual token display, companion connection, channel delivery, task, approval, provider/model, and attachment actions remain explicit visible user flows.',
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function describeHarnessPairingRoute(_context: CommandContext, args: AgentHarnessPairingArgs): PairingResolution {
|
|
239
|
+
const lookup = lookupFromArgs(args);
|
|
240
|
+
if (!lookup) {
|
|
241
|
+
return {
|
|
242
|
+
status: 'missing_lookup',
|
|
243
|
+
usage: 'pairing_route requires pairingRouteId, target, or query. Use mode:"pairing_posture" to inspect pairing route ids.',
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
const routes = pairingRoutes();
|
|
247
|
+
const normalized = lookup.input.toLowerCase();
|
|
248
|
+
const exact = routes.find((route) => route.id === lookup.input);
|
|
249
|
+
if (exact) return { status: 'found', route: describeRoute(exact, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'id' } }) };
|
|
250
|
+
const insensitive = routes.find((route) => route.id.toLowerCase() === normalized);
|
|
251
|
+
if (insensitive) return { status: 'found', route: describeRoute(insensitive, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'case-insensitive-id' } }) };
|
|
252
|
+
const searched = routes.filter((route) => routeSearchText(route).includes(normalized));
|
|
253
|
+
if (searched.length === 1) return { status: 'found', route: describeRoute(searched[0]!, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'search' } }) };
|
|
254
|
+
if (searched.length > 1) {
|
|
255
|
+
return {
|
|
256
|
+
status: 'ambiguous',
|
|
257
|
+
input: lookup.input,
|
|
258
|
+
candidates: searched.slice(0, 8).map(describeCandidate),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
status: 'missing_lookup',
|
|
263
|
+
usage: `Unknown pairing route ${lookup.input}. Use mode:"pairing_posture" to inspect pairing route ids.`,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
@@ -7,6 +7,7 @@ export interface AgentHarnessPanelArgs {
|
|
|
7
7
|
readonly panelId?: unknown;
|
|
8
8
|
readonly target?: unknown;
|
|
9
9
|
readonly category?: unknown;
|
|
10
|
+
readonly includeParameters?: unknown;
|
|
10
11
|
readonly limit?: unknown;
|
|
11
12
|
readonly pane?: unknown;
|
|
12
13
|
}
|
|
@@ -39,6 +40,11 @@ function readLimit(value: unknown, fallback: number): number {
|
|
|
39
40
|
return Math.max(1, Math.min(500, Math.trunc(parsed)));
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
function previewText(value: string, maxLength = 120): string {
|
|
44
|
+
const normalized = value.replace(/\s+/g, ' ').trim();
|
|
45
|
+
return normalized.length <= maxLength ? normalized : `${normalized.slice(0, maxLength - 1).trimEnd()}...`;
|
|
46
|
+
}
|
|
47
|
+
|
|
42
48
|
function panelManager(context: CommandContext) {
|
|
43
49
|
return context.workspace.panelManager ?? null;
|
|
44
50
|
}
|
|
@@ -50,6 +56,7 @@ function panelMatches(panel: Record<string, unknown>, query: string): boolean {
|
|
|
50
56
|
panel.name,
|
|
51
57
|
panel.category,
|
|
52
58
|
panel.description,
|
|
59
|
+
panel.summary,
|
|
53
60
|
panel.workspaceRoute,
|
|
54
61
|
].map((value) => typeof value === 'object' ? JSON.stringify(value) : String(value ?? '')).join('\n').toLowerCase().includes(query.toLowerCase());
|
|
55
62
|
}
|
|
@@ -69,7 +76,7 @@ function panelCandidate(registration: PanelRegistration): Record<string, unknown
|
|
|
69
76
|
id: registration.id,
|
|
70
77
|
name: registration.name,
|
|
71
78
|
category: registration.category,
|
|
72
|
-
|
|
79
|
+
summary: previewText(registration.description),
|
|
73
80
|
workspaceRoute: {
|
|
74
81
|
categoryId: agentWorkspaceCategoryForPanel(registration.id),
|
|
75
82
|
command: agentWorkspaceCommandForPanel(registration.id),
|
|
@@ -103,7 +110,11 @@ function resolveHarnessPanel(context: CommandContext, args: AgentHarnessPanelArg
|
|
|
103
110
|
return null;
|
|
104
111
|
}
|
|
105
112
|
|
|
106
|
-
function describePanelRegistration(
|
|
113
|
+
function describePanelRegistration(
|
|
114
|
+
context: CommandContext,
|
|
115
|
+
registration: PanelRegistration,
|
|
116
|
+
options: { readonly includeParameters?: boolean; readonly lookup?: PanelLookup } = {},
|
|
117
|
+
): Record<string, unknown> {
|
|
107
118
|
const manager = panelManager(context);
|
|
108
119
|
const openPanel = manager?.getPanel(registration.id) ?? null;
|
|
109
120
|
const pane = manager?.getPaneOf(registration.id) ?? null;
|
|
@@ -113,8 +124,8 @@ function describePanelRegistration(context: CommandContext, registration: PanelR
|
|
|
113
124
|
name: registration.name,
|
|
114
125
|
icon: registration.icon,
|
|
115
126
|
category: registration.category,
|
|
116
|
-
description: registration.description,
|
|
117
|
-
...(lookup ? { lookup } : {}),
|
|
127
|
+
...(options.includeParameters ? { description: registration.description } : { summary: previewText(registration.description) }),
|
|
128
|
+
...(options.lookup ? { lookup: options.lookup } : {}),
|
|
118
129
|
preload: registration.preload === true,
|
|
119
130
|
open: openPanel !== null,
|
|
120
131
|
pane,
|
|
@@ -124,11 +135,13 @@ function describePanelRegistration(context: CommandContext, registration: PanelR
|
|
|
124
135
|
categoryId: agentWorkspaceCategoryForPanel(registration.id),
|
|
125
136
|
command: agentWorkspaceCommandForPanel(registration.id),
|
|
126
137
|
},
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
138
|
+
...(options.includeParameters ? {
|
|
139
|
+
policy: {
|
|
140
|
+
effect: 'ui-navigation',
|
|
141
|
+
confirmation: 'agent_harness mode:"open_panel" requires confirm:true and explicitUserRequest.',
|
|
142
|
+
boundary: 'Panels are Agent/TUI operator views. The model can inspect panel catalog/open state; panel routing uses the existing Agent workspace bridge and does not mutate connected-host lifecycle.',
|
|
143
|
+
},
|
|
144
|
+
} : {}),
|
|
132
145
|
};
|
|
133
146
|
}
|
|
134
147
|
|
|
@@ -142,8 +155,9 @@ export function listHarnessPanels(context: CommandContext, args: AgentHarnessPan
|
|
|
142
155
|
const query = readString(args.query);
|
|
143
156
|
const category = readString(args.category);
|
|
144
157
|
const limit = readLimit(args.limit, 200);
|
|
158
|
+
const includeParameters = args.includeParameters === true;
|
|
145
159
|
return manager.getRegisteredTypes()
|
|
146
|
-
.map((registration) => describePanelRegistration(context, registration))
|
|
160
|
+
.map((registration) => describePanelRegistration(context, registration, { includeParameters }))
|
|
147
161
|
.filter((panel) => !category || panel.category === category)
|
|
148
162
|
.filter((panel) => panelMatches(panel, query))
|
|
149
163
|
.sort((a, b) => String(a.id).localeCompare(String(b.id)))
|
|
@@ -152,7 +166,7 @@ export function listHarnessPanels(context: CommandContext, args: AgentHarnessPan
|
|
|
152
166
|
|
|
153
167
|
export function describeHarnessPanel(context: CommandContext, args: AgentHarnessPanelArgs): Record<string, unknown> | null {
|
|
154
168
|
const resolved = resolveHarnessPanel(context, args);
|
|
155
|
-
if (resolved?.status === 'found') return describePanelRegistration(context, resolved.registration, resolved.lookup);
|
|
169
|
+
if (resolved?.status === 'found') return describePanelRegistration(context, resolved.registration, { includeParameters: true, lookup: resolved.lookup });
|
|
156
170
|
if (resolved?.status === 'ambiguous') {
|
|
157
171
|
return { status: 'ambiguous', input: resolved.input, candidates: resolved.candidates };
|
|
158
172
|
}
|
|
@@ -171,7 +185,7 @@ export function openHarnessPanel(context: CommandContext, args: AgentHarnessPane
|
|
|
171
185
|
availablePanels: listHarnessPanels(context, { limit: 50 }).map((entry) => entry.id),
|
|
172
186
|
};
|
|
173
187
|
}
|
|
174
|
-
const panel = describePanelRegistration(context, resolved.registration, resolved.lookup);
|
|
188
|
+
const panel = describePanelRegistration(context, resolved.registration, { includeParameters: true, lookup: resolved.lookup });
|
|
175
189
|
const requestedPane = readString(args.pane);
|
|
176
190
|
const pane = requestedPane === 'bottom' || requestedPane === 'top' ? requestedPane : undefined;
|
|
177
191
|
if (!context.showPanel) {
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import type { CommandContext } from '../input/command-registry.ts';
|
|
2
|
+
import type { ProviderAccountRecord, ProviderAccountSnapshot } from '../panels/provider-account-snapshot.ts';
|
|
3
|
+
import { buildProviderAccountSnapshot } from '../panels/provider-account-snapshot.ts';
|
|
4
|
+
import { requireProvider, requireServiceRegistry, requireSubscriptionManager } from '../input/commands/runtime-services.ts';
|
|
5
|
+
|
|
6
|
+
export interface AgentHarnessProviderAccountArgs {
|
|
7
|
+
readonly providerId?: unknown;
|
|
8
|
+
readonly target?: unknown;
|
|
9
|
+
readonly query?: unknown;
|
|
10
|
+
readonly includeParameters?: unknown;
|
|
11
|
+
readonly limit?: unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
type ProviderAccountLookupSource = 'providerId' | 'target' | 'query';
|
|
15
|
+
|
|
16
|
+
type ProviderAccountResolution =
|
|
17
|
+
| { readonly status: 'found'; readonly account: Record<string, unknown> }
|
|
18
|
+
| { readonly status: 'ambiguous'; readonly input: string; readonly candidates: readonly Record<string, unknown>[] }
|
|
19
|
+
| { readonly status: 'missing_lookup'; readonly usage: string };
|
|
20
|
+
|
|
21
|
+
function readString(value: unknown): string {
|
|
22
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readLimit(value: unknown, fallback: number): number {
|
|
26
|
+
const parsed = typeof value === 'string' && value.trim() ? Number(value) : value;
|
|
27
|
+
if (typeof parsed !== 'number' || !Number.isFinite(parsed)) return fallback;
|
|
28
|
+
return Math.max(1, Math.min(500, Math.trunc(parsed)));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function loadSnapshot(context: CommandContext): Promise<ProviderAccountSnapshot> {
|
|
32
|
+
return await buildProviderAccountSnapshot({
|
|
33
|
+
providerModels: requireProvider(context).providerRegistry,
|
|
34
|
+
services: requireServiceRegistry(context),
|
|
35
|
+
subscriptions: requireSubscriptionManager(context),
|
|
36
|
+
environment: {
|
|
37
|
+
hasEnvironmentVariable: (name: string) => Boolean(process.env[name]),
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function lookupFromArgs(args: AgentHarnessProviderAccountArgs): { readonly source: ProviderAccountLookupSource; readonly input: string } | null {
|
|
43
|
+
const providerId = readString(args.providerId);
|
|
44
|
+
if (providerId) return { source: 'providerId', input: providerId };
|
|
45
|
+
const target = readString(args.target);
|
|
46
|
+
if (target) return { source: 'target', input: target };
|
|
47
|
+
const query = readString(args.query);
|
|
48
|
+
return query ? { source: 'query', input: query } : null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function accountSearchText(account: ProviderAccountRecord): string {
|
|
52
|
+
return [
|
|
53
|
+
account.providerId,
|
|
54
|
+
account.authFreshness,
|
|
55
|
+
account.activeRoute,
|
|
56
|
+
account.preferredRoute,
|
|
57
|
+
account.activeRouteReason,
|
|
58
|
+
account.fallbackRoute ?? '',
|
|
59
|
+
account.fallbackRisk ?? '',
|
|
60
|
+
...account.availableRoutes,
|
|
61
|
+
...account.issues,
|
|
62
|
+
...account.notes,
|
|
63
|
+
...account.recommendedActions,
|
|
64
|
+
...account.usageWindows.flatMap((window) => [window.label, window.detail]),
|
|
65
|
+
...account.routeRecords.flatMap((route) => [route.route, route.freshness, route.detail, ...route.issues]),
|
|
66
|
+
].join('\n').toLowerCase();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function describeCandidate(account: ProviderAccountRecord): Record<string, unknown> {
|
|
70
|
+
return {
|
|
71
|
+
providerId: account.providerId,
|
|
72
|
+
activeRoute: account.activeRoute,
|
|
73
|
+
preferredRoute: account.preferredRoute,
|
|
74
|
+
authFreshness: account.authFreshness,
|
|
75
|
+
configured: account.configured,
|
|
76
|
+
issues: account.issues.length,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function describeAccount(
|
|
81
|
+
account: ProviderAccountRecord,
|
|
82
|
+
options: { readonly includeParameters?: boolean; readonly lookup?: Record<string, unknown> } = {},
|
|
83
|
+
): Record<string, unknown> {
|
|
84
|
+
return {
|
|
85
|
+
providerId: account.providerId,
|
|
86
|
+
active: account.active,
|
|
87
|
+
configured: account.configured,
|
|
88
|
+
modelCount: account.modelCount,
|
|
89
|
+
oauthReady: account.oauthReady,
|
|
90
|
+
pendingLogin: account.pendingLogin,
|
|
91
|
+
availableRoutes: account.availableRoutes,
|
|
92
|
+
preferredRoute: account.preferredRoute,
|
|
93
|
+
activeRoute: account.activeRoute,
|
|
94
|
+
activeRouteReason: account.activeRouteReason,
|
|
95
|
+
authFreshness: account.authFreshness,
|
|
96
|
+
...(account.fallbackRoute ? { fallbackRoute: account.fallbackRoute } : {}),
|
|
97
|
+
...(account.fallbackRisk ? { fallbackRisk: account.fallbackRisk } : {}),
|
|
98
|
+
...(account.expiresAt ? { expiresAt: new Date(account.expiresAt).toISOString() } : {}),
|
|
99
|
+
...(account.tokenType ? { tokenType: account.tokenType } : {}),
|
|
100
|
+
issueCount: account.issues.length,
|
|
101
|
+
recommendedActionCount: account.recommendedActions.length,
|
|
102
|
+
...(options.lookup ? { lookup: options.lookup } : {}),
|
|
103
|
+
...(options.includeParameters ? {
|
|
104
|
+
routeRecords: account.routeRecords.map((route) => ({
|
|
105
|
+
route: route.route,
|
|
106
|
+
usable: route.usable,
|
|
107
|
+
freshness: route.freshness,
|
|
108
|
+
detail: route.detail,
|
|
109
|
+
issues: route.issues,
|
|
110
|
+
})),
|
|
111
|
+
usageWindows: account.usageWindows,
|
|
112
|
+
issues: account.issues,
|
|
113
|
+
notes: account.notes,
|
|
114
|
+
recommendedActions: account.recommendedActions,
|
|
115
|
+
policy: {
|
|
116
|
+
effect: 'read-only',
|
|
117
|
+
values: 'Provider account posture reports route and freshness metadata only; raw tokens, authorization codes, and secret values are never returned.',
|
|
118
|
+
mutation: 'Provider login, logout, subscription bundle export, and account repair actions stay explicit confirmation-gated workspace or slash-command flows.',
|
|
119
|
+
},
|
|
120
|
+
modelAccess: {
|
|
121
|
+
reviewCommand: '/accounts review',
|
|
122
|
+
showCommand: `/accounts show ${account.providerId}`,
|
|
123
|
+
routesCommand: `/accounts routes ${account.providerId}`,
|
|
124
|
+
repairCommand: `/accounts repair ${account.providerId}`,
|
|
125
|
+
subscriptionInspectCommand: `/subscription inspect ${account.providerId}`,
|
|
126
|
+
workspaceActions: [
|
|
127
|
+
'provider-accounts',
|
|
128
|
+
'provider-account-repair',
|
|
129
|
+
'subscription-review',
|
|
130
|
+
'subscription-inspect',
|
|
131
|
+
],
|
|
132
|
+
loginStartCommand: `/subscription login ${account.providerId} start --yes`,
|
|
133
|
+
loginFinishCommand: `/subscription login ${account.providerId} finish <code-or-url> --yes`,
|
|
134
|
+
logoutCommand: `/subscription logout ${account.providerId} --yes`,
|
|
135
|
+
confirmationRequired: true,
|
|
136
|
+
},
|
|
137
|
+
} : {}),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function providerAccountCatalogStatus(context: CommandContext): Promise<Record<string, unknown>> {
|
|
142
|
+
const snapshot = await loadSnapshot(context);
|
|
143
|
+
return {
|
|
144
|
+
modes: ['provider_accounts', 'provider_account'],
|
|
145
|
+
providers: snapshot.providers.length,
|
|
146
|
+
configured: snapshot.configuredCount,
|
|
147
|
+
issues: snapshot.issueCount,
|
|
148
|
+
readOnly: true,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function providerAccountSummary(
|
|
153
|
+
context: CommandContext,
|
|
154
|
+
args: AgentHarnessProviderAccountArgs,
|
|
155
|
+
): Promise<Record<string, unknown>> {
|
|
156
|
+
const snapshot = await loadSnapshot(context);
|
|
157
|
+
const query = readString(args.query).toLowerCase();
|
|
158
|
+
const includeParameters = args.includeParameters === true;
|
|
159
|
+
const accounts = snapshot.providers
|
|
160
|
+
.filter((account) => !query || accountSearchText(account).includes(query))
|
|
161
|
+
.slice(0, readLimit(args.limit, 100));
|
|
162
|
+
return {
|
|
163
|
+
capturedAt: new Date(snapshot.capturedAt).toISOString(),
|
|
164
|
+
providers: accounts.map((account) => describeAccount(account, { includeParameters })),
|
|
165
|
+
returned: accounts.length,
|
|
166
|
+
total: snapshot.providers.length,
|
|
167
|
+
configured: snapshot.configuredCount,
|
|
168
|
+
issues: snapshot.issueCount,
|
|
169
|
+
policy: 'Read-only provider account posture. Use confirmed workspace actions or slash-command mirrors for login/logout/bundle mutations.',
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export async function describeHarnessProviderAccount(
|
|
174
|
+
context: CommandContext,
|
|
175
|
+
args: AgentHarnessProviderAccountArgs,
|
|
176
|
+
): Promise<ProviderAccountResolution> {
|
|
177
|
+
const lookup = lookupFromArgs(args);
|
|
178
|
+
if (!lookup) {
|
|
179
|
+
return {
|
|
180
|
+
status: 'missing_lookup',
|
|
181
|
+
usage: 'provider_account requires providerId, target, or query. Use mode:"provider_accounts" to inspect provider ids.',
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const snapshot = await loadSnapshot(context);
|
|
185
|
+
const normalized = lookup.input.toLowerCase();
|
|
186
|
+
const exact = snapshot.providers.find((account) => account.providerId === lookup.input);
|
|
187
|
+
if (exact) return { status: 'found', account: describeAccount(exact, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'id' } }) };
|
|
188
|
+
const insensitive = snapshot.providers.find((account) => account.providerId.toLowerCase() === normalized);
|
|
189
|
+
if (insensitive) return { status: 'found', account: describeAccount(insensitive, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'case-insensitive-id' } }) };
|
|
190
|
+
const searched = snapshot.providers.filter((account) => accountSearchText(account).includes(normalized));
|
|
191
|
+
if (searched.length === 1) {
|
|
192
|
+
return { status: 'found', account: describeAccount(searched[0]!, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'search' } }) };
|
|
193
|
+
}
|
|
194
|
+
if (searched.length > 1) {
|
|
195
|
+
return {
|
|
196
|
+
status: 'ambiguous',
|
|
197
|
+
input: lookup.input,
|
|
198
|
+
candidates: searched.slice(0, 8).map(describeCandidate),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
status: 'missing_lookup',
|
|
203
|
+
usage: `Unknown provider account ${lookup.input}. Use mode:"provider_accounts" to inspect provider ids.`,
|
|
204
|
+
};
|
|
205
|
+
}
|