@pellux/goodvibes-agent 1.0.30 → 1.0.33
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 +42 -1
- package/README.md +7 -5
- package/dist/package/main.js +7323 -2268
- package/docs/README.md +2 -2
- package/docs/channels-remote-and-api.md +6 -2
- package/docs/connected-host.md +26 -5
- package/docs/getting-started.md +30 -10
- 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 +20 -8
- 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 +28 -0
- package/release/release-readiness.json +581 -0
- 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 +2 -2
- package/src/runtime/connected-host-auth.ts +16 -0
- package/src/tools/agent-channel-send-tool.ts +5 -7
- package/src/tools/agent-harness-channel-metadata.ts +177 -0
- package/src/tools/agent-harness-connected-host-status.ts +1 -1
- 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 +246 -0
- package/src/tools/agent-harness-media-posture.ts +282 -0
- package/src/tools/agent-harness-metadata.ts +21 -4
- package/src/tools/agent-harness-model-routing.ts +501 -0
- 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-provider-account-metadata.ts +203 -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 +646 -0
- package/src/tools/agent-harness-service-posture.ts +201 -0
- package/src/tools/agent-harness-session-metadata.ts +282 -0
- package/src/tools/agent-harness-setup-posture.ts +295 -0
- package/src/tools/agent-harness-tool-schema.ts +103 -27
- package/src/tools/agent-harness-tool.ts +209 -236
- package/src/tools/agent-harness-ui-surface-metadata.ts +1 -1
- package/src/tools/agent-harness-workspace-actions.ts +232 -0
- package/src/tools/agent-knowledge-ingest-tool.ts +6 -8
- package/src/tools/agent-knowledge-tool.ts +122 -23
- package/src/tools/agent-local-registry-tool.ts +4 -5
- package/src/tools/agent-media-generate-tool.ts +4 -6
- package/src/tools/agent-notify-tool.ts +5 -6
- package/src/tools/agent-operator-action-tool.ts +6 -8
- package/src/tools/agent-operator-briefing-tool.ts +3 -4
- package/src/tools/agent-reminder-schedule-tool.ts +6 -7
- package/src/tools/agent-tool-policy-guard.ts +8 -17
- package/src/tools/agent-work-plan-tool.ts +3 -4
- 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',
|
|
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',
|
|
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
|
+
policy: {
|
|
170
|
+
effect: route.effect,
|
|
171
|
+
values: 'Pairing posture returns endpoint binding and token fingerprint only; raw companion tokens and QR payloads are never returned by this read-only mode.',
|
|
172
|
+
mutation: 'Pairing display, manual token display, companion connection, channel sends, provider/model changes, approval actions, and attachment flows stay explicit visible user flows.',
|
|
173
|
+
},
|
|
174
|
+
...(options.includeParameters ? {
|
|
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
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
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
|
+
routeRecords: account.routeRecords.map((route) => ({
|
|
101
|
+
route: route.route,
|
|
102
|
+
usable: route.usable,
|
|
103
|
+
freshness: route.freshness,
|
|
104
|
+
detail: route.detail,
|
|
105
|
+
issues: route.issues,
|
|
106
|
+
})),
|
|
107
|
+
usageWindows: account.usageWindows,
|
|
108
|
+
issues: account.issues,
|
|
109
|
+
notes: account.notes,
|
|
110
|
+
recommendedActions: account.recommendedActions,
|
|
111
|
+
...(options.lookup ? { lookup: options.lookup } : {}),
|
|
112
|
+
policy: {
|
|
113
|
+
effect: 'read-only',
|
|
114
|
+
values: 'Provider account posture reports route and freshness metadata only; raw tokens, authorization codes, and secret values are never returned.',
|
|
115
|
+
mutation: 'Provider login, logout, subscription bundle export, and account repair actions stay explicit confirmation-gated workspace or slash-command flows.',
|
|
116
|
+
},
|
|
117
|
+
modelAccess: {
|
|
118
|
+
reviewCommand: '/accounts review',
|
|
119
|
+
showCommand: `/accounts show ${account.providerId}`,
|
|
120
|
+
routesCommand: `/accounts routes ${account.providerId}`,
|
|
121
|
+
repairCommand: `/accounts repair ${account.providerId}`,
|
|
122
|
+
subscriptionInspectCommand: `/subscription inspect ${account.providerId}`,
|
|
123
|
+
workspaceActions: [
|
|
124
|
+
'provider-accounts',
|
|
125
|
+
'provider-account-repair',
|
|
126
|
+
'subscription-review',
|
|
127
|
+
'subscription-inspect',
|
|
128
|
+
],
|
|
129
|
+
...(options.includeParameters ? {
|
|
130
|
+
loginStartCommand: `/subscription login ${account.providerId} start --yes`,
|
|
131
|
+
loginFinishCommand: `/subscription login ${account.providerId} finish <code-or-url> --yes`,
|
|
132
|
+
logoutCommand: `/subscription logout ${account.providerId} --yes`,
|
|
133
|
+
confirmationRequired: true,
|
|
134
|
+
} : {}),
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export async function providerAccountCatalogStatus(context: CommandContext): Promise<Record<string, unknown>> {
|
|
140
|
+
const snapshot = await loadSnapshot(context);
|
|
141
|
+
return {
|
|
142
|
+
modes: ['provider_accounts', 'provider_account'],
|
|
143
|
+
providers: snapshot.providers.length,
|
|
144
|
+
configured: snapshot.configuredCount,
|
|
145
|
+
issues: snapshot.issueCount,
|
|
146
|
+
readOnly: true,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export async function providerAccountSummary(
|
|
151
|
+
context: CommandContext,
|
|
152
|
+
args: AgentHarnessProviderAccountArgs,
|
|
153
|
+
): Promise<Record<string, unknown>> {
|
|
154
|
+
const snapshot = await loadSnapshot(context);
|
|
155
|
+
const query = readString(args.query).toLowerCase();
|
|
156
|
+
const includeParameters = args.includeParameters === true;
|
|
157
|
+
const accounts = snapshot.providers
|
|
158
|
+
.filter((account) => !query || accountSearchText(account).includes(query))
|
|
159
|
+
.slice(0, readLimit(args.limit, 100));
|
|
160
|
+
return {
|
|
161
|
+
capturedAt: new Date(snapshot.capturedAt).toISOString(),
|
|
162
|
+
providers: accounts.map((account) => describeAccount(account, { includeParameters })),
|
|
163
|
+
returned: accounts.length,
|
|
164
|
+
total: snapshot.providers.length,
|
|
165
|
+
configured: snapshot.configuredCount,
|
|
166
|
+
issues: snapshot.issueCount,
|
|
167
|
+
policy: 'Read-only provider account posture. Use confirmed workspace actions or slash-command mirrors for login/logout/bundle mutations.',
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export async function describeHarnessProviderAccount(
|
|
172
|
+
context: CommandContext,
|
|
173
|
+
args: AgentHarnessProviderAccountArgs,
|
|
174
|
+
): Promise<ProviderAccountResolution> {
|
|
175
|
+
const lookup = lookupFromArgs(args);
|
|
176
|
+
if (!lookup) {
|
|
177
|
+
return {
|
|
178
|
+
status: 'missing_lookup',
|
|
179
|
+
usage: 'provider_account requires providerId, target, or query. Use mode:"provider_accounts" to inspect provider ids.',
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
const snapshot = await loadSnapshot(context);
|
|
183
|
+
const normalized = lookup.input.toLowerCase();
|
|
184
|
+
const exact = snapshot.providers.find((account) => account.providerId === lookup.input);
|
|
185
|
+
if (exact) return { status: 'found', account: describeAccount(exact, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'id' } }) };
|
|
186
|
+
const insensitive = snapshot.providers.find((account) => account.providerId.toLowerCase() === normalized);
|
|
187
|
+
if (insensitive) return { status: 'found', account: describeAccount(insensitive, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'case-insensitive-id' } }) };
|
|
188
|
+
const searched = snapshot.providers.filter((account) => accountSearchText(account).includes(normalized));
|
|
189
|
+
if (searched.length === 1) {
|
|
190
|
+
return { status: 'found', account: describeAccount(searched[0]!, { includeParameters: true, lookup: { ...lookup, resolvedBy: 'search' } }) };
|
|
191
|
+
}
|
|
192
|
+
if (searched.length > 1) {
|
|
193
|
+
return {
|
|
194
|
+
status: 'ambiguous',
|
|
195
|
+
input: lookup.input,
|
|
196
|
+
candidates: searched.slice(0, 8).map(describeCandidate),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
status: 'missing_lookup',
|
|
201
|
+
usage: `Unknown provider account ${lookup.input}. Use mode:"provider_accounts" to inspect provider ids.`,
|
|
202
|
+
};
|
|
203
|
+
}
|