@pellux/goodvibes-agent 0.1.80 → 0.1.82
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 +11 -0
- package/package.json +3 -1
- package/src/agent/skill-registry.ts +255 -5
- package/src/cli/help.ts +4 -4
- package/src/cli/management.ts +54 -136
- package/src/cli/status.ts +10 -10
- package/src/input/agent-workspace-categories.ts +2 -0
- package/src/input/agent-workspace-setup.ts +6 -4
- package/src/input/agent-workspace-snapshot.ts +26 -3
- package/src/input/agent-workspace-types.ts +4 -0
- package/src/input/commands/agent-skills-runtime.ts +151 -3
- package/src/input/commands/health-runtime.ts +12 -14
- package/src/input/commands/platform-access-runtime.ts +8 -4
- package/src/input/commands.ts +0 -2
- package/src/input/onboarding/onboarding-wizard-helpers.ts +1 -1
- package/src/panels/builtin/operations.ts +0 -10
- package/src/panels/provider-health-domains.ts +9 -8
- package/src/renderer/agent-workspace.ts +9 -6
- package/src/runtime/onboarding/apply.ts +6 -154
- package/src/runtime/onboarding/derivation.ts +4 -4
- package/src/runtime/onboarding/types.ts +1 -11
- package/src/runtime/onboarding/verify.ts +3 -25
- package/src/version.ts +1 -1
- package/src/input/commands/local-auth-runtime.ts +0 -128
- package/src/panels/local-auth-panel.ts +0 -130
|
@@ -8,7 +8,6 @@ import { getSettingsControlPlaneSnapshot } from '@/runtime/index.ts';
|
|
|
8
8
|
import { checkRecoveryFile, readLastSessionPointer } from '@/runtime/index.ts';
|
|
9
9
|
import {
|
|
10
10
|
openCommandPanel,
|
|
11
|
-
requireLocalUserAuthManager,
|
|
12
11
|
requireOperatorClient,
|
|
13
12
|
requireProviderApi,
|
|
14
13
|
requireReadModels,
|
|
@@ -78,12 +77,13 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
|
|
|
78
77
|
if (sub === 'auth') {
|
|
79
78
|
const auth = readModels.localAuth.getSnapshot();
|
|
80
79
|
ctx.print([
|
|
81
|
-
'Health Review:
|
|
82
|
-
|
|
83
|
-
`
|
|
84
|
-
`
|
|
85
|
-
|
|
86
|
-
|
|
80
|
+
'Health Review: Runtime Auth',
|
|
81
|
+
' owner: external GoodVibes runtime host',
|
|
82
|
+
` compatibility users visible: ${auth.userCount}`,
|
|
83
|
+
` compatibility sessions visible: ${auth.sessionCount}`,
|
|
84
|
+
` bootstrap file signal: ${auth.bootstrapCredentialPresent ? 'present' : 'cleared'}`,
|
|
85
|
+
' Agent action: review provider/subscription auth only; do not mutate runtime auth users or bootstrap credentials.',
|
|
86
|
+
...(auth.bootstrapCredentialPresent ? [' issue: bootstrap cleanup belongs to the runtime-owning TUI or host tooling'] : []),
|
|
87
87
|
].join('\n'));
|
|
88
88
|
return;
|
|
89
89
|
}
|
|
@@ -222,13 +222,11 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
|
|
|
222
222
|
));
|
|
223
223
|
lines.push(' verify: /health settings');
|
|
224
224
|
} else if (domain === 'auth') {
|
|
225
|
-
const auth = requireLocalUserAuthManager(ctx).inspect();
|
|
226
225
|
lines.push(' domain: auth');
|
|
227
|
-
lines.push(
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
));
|
|
226
|
+
lines.push(' /auth review');
|
|
227
|
+
lines.push(' /providers');
|
|
228
|
+
lines.push(' /subscription providers');
|
|
229
|
+
lines.push(' runtime auth users/bootstrap cleanup: use the runtime-owning GoodVibes TUI or host tooling');
|
|
232
230
|
lines.push(' verify: /health auth');
|
|
233
231
|
} else if (domain === 'accounts') {
|
|
234
232
|
lines.push(' domain: accounts');
|
|
@@ -316,7 +314,7 @@ export function registerHealthRuntimeCommands(registry: CommandRegistry): void {
|
|
|
316
314
|
` account issues: ${accountSnapshot.issueCount}`,
|
|
317
315
|
` settings conflicts: ${settingsSnapshot.conflicts.length}`,
|
|
318
316
|
` managed locks: ${settingsSnapshot.managedLockCount}`,
|
|
319
|
-
`
|
|
317
|
+
` runtime auth owner: external`,
|
|
320
318
|
` remote workers: ${snapshot.remoteRunnerCount}`,
|
|
321
319
|
...formatSessionMaintenanceLines(maintenance, 'guided').map((line) => ` ${line}`),
|
|
322
320
|
...(snapshot.issues.length > 0 ? ['', ...snapshot.issues.map((issue) => ` [${issue.severity.toUpperCase()}] ${issue.area}: ${issue.message}`)] : []),
|
|
@@ -2,7 +2,6 @@ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
3
|
import type { CommandRegistry } from '../command-registry.ts';
|
|
4
4
|
import { listBuiltinSubscriptionProviders } from '@pellux/goodvibes-sdk/platform/config';
|
|
5
|
-
import { handleLocalAuthCommand } from './local-auth-runtime.ts';
|
|
6
5
|
import { buildAuthInspectionSnapshot, inspectProviderAuth } from '@/runtime/index.ts';
|
|
7
6
|
import { requireSecretsManager, requireServiceRegistry, requireShellPaths, requireSubscriptionManager } from './runtime-services.ts';
|
|
8
7
|
import { requireYesFlag, stripYesFlag } from './confirmation.ts';
|
|
@@ -113,7 +112,7 @@ export function registerPlatformAccessRuntimeCommands(registry: CommandRegistry)
|
|
|
113
112
|
registry.register({
|
|
114
113
|
name: 'auth',
|
|
115
114
|
description: 'Review auth posture and exchange session login tokens with local services',
|
|
116
|
-
usage: '[review|show <provider>|repair <provider>|bundle export <path> --yes|bundle inspect <path>|login <runtime|listener> <baseUrl> <username> <password> [secretKey] --yes
|
|
115
|
+
usage: '[review|show <provider>|repair <provider>|bundle export <path> --yes|bundle inspect <path>|login <runtime|listener> <baseUrl> <username> <password> [secretKey] --yes]',
|
|
117
116
|
async handler(args, ctx) {
|
|
118
117
|
const parsed = stripYesFlag(args);
|
|
119
118
|
const commandArgs = [...parsed.rest];
|
|
@@ -123,7 +122,12 @@ export function registerPlatformAccessRuntimeCommands(registry: CommandRegistry)
|
|
|
123
122
|
const serviceRegistry = requireServiceRegistry(ctx);
|
|
124
123
|
const secretsManager = requireSecretsManager(ctx);
|
|
125
124
|
if (sub === 'local') {
|
|
126
|
-
|
|
125
|
+
ctx.print([
|
|
126
|
+
'Local runtime auth management is external to GoodVibes Agent.',
|
|
127
|
+
'Agent connects to an already-running GoodVibes runtime and does not create, delete, rotate, revoke, or clear runtime auth users, sessions, or bootstrap credentials.',
|
|
128
|
+
'Use the runtime-owning GoodVibes TUI or host tooling for runtime auth administration.',
|
|
129
|
+
'Agent auth commands available here: /auth review, /auth show <provider>, /auth repair <provider>, /auth login <runtime|listener> ... --yes.',
|
|
130
|
+
].join('\n'));
|
|
127
131
|
return;
|
|
128
132
|
}
|
|
129
133
|
if (sub === 'review') {
|
|
@@ -270,7 +274,7 @@ export function registerPlatformAccessRuntimeCommands(registry: CommandRegistry)
|
|
|
270
274
|
return;
|
|
271
275
|
}
|
|
272
276
|
|
|
273
|
-
ctx.print('Usage: /auth [review|show <provider>|bundle export <path> --yes|bundle inspect <path>|login <runtime|listener> <baseUrl> <username> <password> [secretKey] --yes
|
|
277
|
+
ctx.print('Usage: /auth [review|show <provider>|bundle export <path> --yes|bundle inspect <path>|login <runtime|listener> <baseUrl> <username> <password> [secretKey] --yes]');
|
|
274
278
|
},
|
|
275
279
|
});
|
|
276
280
|
}
|
package/src/input/commands.ts
CHANGED
|
@@ -23,7 +23,6 @@ import { registerTasksRuntimeCommands } from './commands/tasks-runtime.ts';
|
|
|
23
23
|
import { registerLocalProviderRuntimeCommands } from './commands/local-provider-runtime.ts';
|
|
24
24
|
import { registerHealthRuntimeCommands } from './commands/health-runtime.ts';
|
|
25
25
|
import { registerProviderAccountsRuntimeCommands } from './commands/provider-accounts-runtime.ts';
|
|
26
|
-
import { registerLocalAuthRuntimeCommands } from './commands/local-auth-runtime.ts';
|
|
27
26
|
import { registerConversationRuntimeCommands } from './commands/conversation-runtime.ts';
|
|
28
27
|
import { registerQrcodeRuntimeCommands } from './commands/qrcode-runtime.ts';
|
|
29
28
|
import { registerOnboardingRuntimeCommands } from './commands/onboarding-runtime.ts';
|
|
@@ -62,7 +61,6 @@ export function registerBuiltinCommands(registry: CommandRegistry): void {
|
|
|
62
61
|
registerLocalProviderRuntimeCommands(registry);
|
|
63
62
|
registerHealthRuntimeCommands(registry);
|
|
64
63
|
registerProviderAccountsRuntimeCommands(registry);
|
|
65
|
-
registerLocalAuthRuntimeCommands(registry);
|
|
66
64
|
registerConversationRuntimeCommands(registry);
|
|
67
65
|
registerQrcodeRuntimeCommands(registry);
|
|
68
66
|
registerOnboardingRuntimeCommands(registry);
|
|
@@ -49,7 +49,7 @@ export function buildDefaultDerivedState(): OnboardingStepDerivationState {
|
|
|
49
49
|
required: false,
|
|
50
50
|
accepted: false,
|
|
51
51
|
reason: 'not-needed',
|
|
52
|
-
detail: 'No
|
|
52
|
+
detail: 'No external runtime auth signal needs confirmation.',
|
|
53
53
|
},
|
|
54
54
|
},
|
|
55
55
|
};
|
|
@@ -2,7 +2,6 @@ import type { PanelManager } from '../panel-manager.ts';
|
|
|
2
2
|
import { ApprovalPanel } from '../approval-panel.ts';
|
|
3
3
|
import { AutomationControlPanel } from '../automation-control-panel.ts';
|
|
4
4
|
import { SubscriptionPanel } from '../subscription-panel.ts';
|
|
5
|
-
import { LocalAuthPanel } from '../local-auth-panel.ts';
|
|
6
5
|
import { ProviderAccountsPanel } from '../provider-accounts-panel.ts';
|
|
7
6
|
import { SecurityPanel } from '../security-panel.ts';
|
|
8
7
|
import { TasksPanel } from '../tasks-panel.ts';
|
|
@@ -59,15 +58,6 @@ export function registerOperationsPanels(manager: PanelManager, deps: ResolvedBu
|
|
|
59
58
|
factory: () => new SubscriptionPanel(deps.serviceRegistry, deps.subscriptionManager),
|
|
60
59
|
});
|
|
61
60
|
|
|
62
|
-
manager.registerType({
|
|
63
|
-
id: 'local-auth',
|
|
64
|
-
name: 'Local Auth',
|
|
65
|
-
icon: 'U',
|
|
66
|
-
category: 'monitoring',
|
|
67
|
-
description: 'Local runtime auth users, bootstrap posture, and active sessions',
|
|
68
|
-
factory: () => new LocalAuthPanel(deps.localUserAuthManager),
|
|
69
|
-
});
|
|
70
|
-
|
|
71
61
|
manager.registerType({
|
|
72
62
|
id: 'accounts',
|
|
73
63
|
name: 'Accounts',
|
|
@@ -44,18 +44,19 @@ export function buildProviderHealthDomainSummaries(
|
|
|
44
44
|
|
|
45
45
|
summaries.push({
|
|
46
46
|
name: 'auth',
|
|
47
|
-
level: auth.bootstrapCredentialPresent
|
|
47
|
+
level: auth.bootstrapCredentialPresent ? 'warn' : 'info',
|
|
48
48
|
summary: auth.bootstrapCredentialPresent
|
|
49
|
-
? 'bootstrap credential
|
|
50
|
-
:
|
|
51
|
-
next:
|
|
49
|
+
? 'external runtime bootstrap credential visible in local compatibility state'
|
|
50
|
+
: 'runtime auth administration belongs to the external runtime owner',
|
|
51
|
+
next: '/auth review',
|
|
52
52
|
details: [
|
|
53
|
-
|
|
54
|
-
auth.userCount
|
|
53
|
+
'GoodVibes Agent does not create, delete, rotate, revoke, or clear runtime auth users or sessions.',
|
|
54
|
+
`${auth.userCount} compatibility user record(s) and ${auth.sessionCount} session record(s) are visible for diagnostics only.`,
|
|
55
|
+
auth.bootstrapCredentialPresent ? 'Runtime bootstrap cleanup must be done from the runtime-owning TUI or host tooling.' : '',
|
|
55
56
|
].filter(Boolean),
|
|
56
57
|
nextSteps: auth.bootstrapCredentialPresent
|
|
57
|
-
? ['/auth
|
|
58
|
-
: ['/auth
|
|
58
|
+
? ['/auth review', '/providers', '/subscription providers']
|
|
59
|
+
: ['/auth review', '/providers'],
|
|
59
60
|
});
|
|
60
61
|
|
|
61
62
|
const settingIssueCount = settings.conflictCount + settings.recentFailureCount + (settings.hasStagedManagedBundle ? 1 : 0);
|
|
@@ -196,10 +196,10 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
|
|
|
196
196
|
base.push(
|
|
197
197
|
{ text: `GoodVibes runtime: ${snapshot.daemonBaseUrl}`, fg: PALETTE.info },
|
|
198
198
|
{ text: `Runtime owner: ${snapshot.daemonOwnership}; Agent connects but never starts or restarts it`, fg: PALETTE.good },
|
|
199
|
+
...setupChecklistLines(snapshot),
|
|
200
|
+
{ text: '' },
|
|
199
201
|
{ text: `Workspace: ${snapshot.workingDirectory}`, fg: PALETTE.muted },
|
|
200
202
|
{ text: `Home: ${snapshot.homeDirectory}`, fg: PALETTE.muted },
|
|
201
|
-
{ text: '' },
|
|
202
|
-
...setupChecklistLines(snapshot),
|
|
203
203
|
);
|
|
204
204
|
} else if (category.id === 'channels') {
|
|
205
205
|
const enabledCount = snapshot.channels.filter((channel) => channel.enabled).length;
|
|
@@ -283,10 +283,11 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
|
|
|
283
283
|
base.push(
|
|
284
284
|
{ text: `Active Agent profile: ${snapshot.activeRuntimeProfile}`, fg: PALETTE.info },
|
|
285
285
|
{ text: `Agent profiles under this home: ${snapshot.runtimeProfileCount}`, fg: PALETTE.info },
|
|
286
|
-
{ text: `Agent profile root: ${snapshot.runtimeProfileRoot}`, fg: PALETTE.muted },
|
|
287
286
|
{ text: `Starter templates: ${snapshot.runtimeStarterTemplateCount}; local custom: ${snapshot.localStarterTemplateCount}`, fg: PALETTE.info },
|
|
288
287
|
{ text: `Config profiles: ${snapshot.configProfileCount}`, fg: PALETTE.info },
|
|
289
288
|
{ text: `Starter ids: ${snapshot.runtimeStarterTemplates.map((template) => template.id).join(', ') || 'none'}`, fg: PALETTE.info },
|
|
289
|
+
{ text: 'Starter Templates', fg: PALETTE.title, bold: true },
|
|
290
|
+
{ text: `Agent profile root: ${snapshot.runtimeProfileRoot}`, fg: PALETTE.muted },
|
|
290
291
|
{ text: '' },
|
|
291
292
|
...profileLines(snapshot),
|
|
292
293
|
{ text: '' },
|
|
@@ -301,7 +302,7 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
|
|
|
301
302
|
base.push(
|
|
302
303
|
{ text: `Session memories: ${snapshot.sessionMemoryCount}`, fg: PALETTE.info },
|
|
303
304
|
{ text: `Local routines: ${snapshot.localRoutineCount}; enabled: ${snapshot.enabledRoutineCount}`, fg: PALETTE.info },
|
|
304
|
-
{ text: `Local skills: ${snapshot.localSkillCount}; enabled: ${snapshot.enabledSkillCount}`, fg: PALETTE.info },
|
|
305
|
+
{ text: `Local skills: ${snapshot.localSkillCount}; enabled: ${snapshot.enabledSkillCount}; bundles: ${snapshot.localSkillBundleCount}; active skills: ${snapshot.activeSkillCount}`, fg: PALETTE.info },
|
|
305
306
|
{ text: `Local personas: ${snapshot.localPersonaCount}; active: ${snapshot.activePersonaName}`, fg: PALETTE.info },
|
|
306
307
|
{ text: 'Durable memory, routines, skills, and personas remain Agent-local until shared registry contracts exist.', fg: PALETTE.good },
|
|
307
308
|
{ text: 'Secrets are rejected/redacted; store secret references instead of secret values.', fg: PALETTE.warn },
|
|
@@ -316,11 +317,13 @@ function snapshotLines(workspace: AgentWorkspace, category: AgentWorkspaceCatego
|
|
|
316
317
|
);
|
|
317
318
|
} else if (category.id === 'skills') {
|
|
318
319
|
base.push(
|
|
319
|
-
{ text: `Skills: ${snapshot.localSkillCount}; enabled: ${snapshot.enabledSkillCount}`, fg: PALETTE.info },
|
|
320
|
+
{ text: `Skills: ${snapshot.localSkillCount}; enabled: ${snapshot.enabledSkillCount}; bundles: ${snapshot.localSkillBundleCount}; enabled bundles: ${snapshot.enabledSkillBundleCount}; active skills: ${snapshot.activeSkillCount}`, fg: PALETTE.info },
|
|
320
321
|
{ text: 'Skills are reusable local procedures the assistant can apply from the main conversation.', fg: PALETTE.good },
|
|
321
|
-
{ text: 'Enabled skills are injected as operating guidance; secret-looking content is rejected.', fg: PALETTE.warn },
|
|
322
|
+
{ text: 'Enabled skills and enabled bundles are injected as operating guidance; secret-looking content is rejected.', fg: PALETTE.warn },
|
|
322
323
|
{ text: '' },
|
|
323
324
|
...localLibraryLines('Skill Library', snapshot.localSkills, 'No local skills yet. Create one here with Create skill.', workspace.selectedLocalLibraryItem('skill')?.id ?? null),
|
|
325
|
+
{ text: '' },
|
|
326
|
+
...localLibraryLines('Skill Bundles', snapshot.localSkillBundles, 'No local skill bundles yet. Use Skill bundles and Create bundle after creating skills.', null),
|
|
324
327
|
);
|
|
325
328
|
} else if (category.id === 'routines') {
|
|
326
329
|
base.push(
|
|
@@ -62,22 +62,6 @@ function setNestedValue(root: Record<string, unknown>, key: string, value: unkno
|
|
|
62
62
|
|
|
63
63
|
type RollbackAction = () => Promise<void> | void;
|
|
64
64
|
|
|
65
|
-
interface BootstrapCredential {
|
|
66
|
-
readonly username: string;
|
|
67
|
-
readonly password: string;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
interface PersistedAuthUser {
|
|
71
|
-
readonly username: string;
|
|
72
|
-
readonly passwordHash: string;
|
|
73
|
-
readonly roles?: readonly string[];
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
interface MutableAuthManager {
|
|
77
|
-
readonly users?: Map<string, PersistedAuthUser>;
|
|
78
|
-
readonly sessions?: Map<string, { readonly token: string; readonly username: string; readonly expiresAt: number }>;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
65
|
function restoreFile(path: string, previous: string | null, reload?: () => void): void {
|
|
82
66
|
if (previous === null) {
|
|
83
67
|
if (existsSync(path)) unlinkSync(path);
|
|
@@ -88,31 +72,6 @@ function restoreFile(path: string, previous: string | null, reload?: () => void)
|
|
|
88
72
|
reload?.();
|
|
89
73
|
}
|
|
90
74
|
|
|
91
|
-
function parseBootstrapCredential(content: string | null): BootstrapCredential | null {
|
|
92
|
-
if (content === null) return null;
|
|
93
|
-
let username = '';
|
|
94
|
-
let password = '';
|
|
95
|
-
for (const rawLine of content.split('\n')) {
|
|
96
|
-
const line = rawLine.trim();
|
|
97
|
-
if (line.startsWith('username=')) username = line.slice('username='.length);
|
|
98
|
-
if (line.startsWith('password=')) password = line.slice('password='.length);
|
|
99
|
-
}
|
|
100
|
-
return username.length > 0 && password.length > 0 ? { username, password } : null;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function parsePersistedAuthUsers(content: string): readonly PersistedAuthUser[] {
|
|
104
|
-
const parsed = JSON.parse(content) as unknown;
|
|
105
|
-
if (!isPlainObject(parsed) || parsed.version !== 1 || !Array.isArray(parsed.users)) {
|
|
106
|
-
throw new Error('Expected a version 1 local auth user store.');
|
|
107
|
-
}
|
|
108
|
-
return parsed.users.filter((user): user is PersistedAuthUser => (
|
|
109
|
-
isPlainObject(user)
|
|
110
|
-
&& typeof user.username === 'string'
|
|
111
|
-
&& typeof user.passwordHash === 'string'
|
|
112
|
-
&& (user.roles === undefined || (Array.isArray(user.roles) && user.roles.every((role) => typeof role === 'string')))
|
|
113
|
-
));
|
|
114
|
-
}
|
|
115
|
-
|
|
116
75
|
function snapshotFileRollback(path: string, reload?: () => void): RollbackAction {
|
|
117
76
|
const previous = existsSync(path) ? readFileSync(path, 'utf-8') : null;
|
|
118
77
|
return () => restoreFile(path, previous, reload);
|
|
@@ -227,18 +186,10 @@ function validateSecretOperation(
|
|
|
227
186
|
}
|
|
228
187
|
|
|
229
188
|
function validateAuthOperation(
|
|
230
|
-
|
|
231
|
-
|
|
189
|
+
_deps: OnboardingApplyDependencies,
|
|
190
|
+
_operation: Extract<OnboardingApplyOperation, { kind: 'ensure-auth-user' }>,
|
|
232
191
|
): void {
|
|
233
|
-
|
|
234
|
-
if (operation.username.trim().length === 0) throw new Error('Local auth username is required.');
|
|
235
|
-
if (operation.password.length === 0) throw new Error(`Local auth password for ${operation.username} is required.`);
|
|
236
|
-
const username = operation.username.trim();
|
|
237
|
-
const existing = deps.auth.inspect().users.find((user) => user.username === username);
|
|
238
|
-
const requiredRoles = operation.roles ?? ['admin'];
|
|
239
|
-
if (existing && !requiredRoles.every((role) => existing.roles.includes(role))) {
|
|
240
|
-
throw new Error(`Existing local auth user ${username} is missing required role(s): ${requiredRoles.join(', ')}.`);
|
|
241
|
-
}
|
|
192
|
+
throw new Error('Runtime auth user/session administration is external to GoodVibes Agent onboarding.');
|
|
242
193
|
}
|
|
243
194
|
|
|
244
195
|
function validateAcknowledgementOperation(
|
|
@@ -313,44 +264,6 @@ async function applySecretOperation(
|
|
|
313
264
|
};
|
|
314
265
|
}
|
|
315
266
|
|
|
316
|
-
function applyAuthOperation(
|
|
317
|
-
deps: OnboardingApplyDependencies,
|
|
318
|
-
operation: Extract<OnboardingApplyOperation, { kind: 'ensure-auth-user' }>,
|
|
319
|
-
): OnboardingAppliedOperation {
|
|
320
|
-
validateAuthOperation(deps, operation);
|
|
321
|
-
const auth = deps.auth!;
|
|
322
|
-
const username = operation.username.trim();
|
|
323
|
-
const before = auth.inspect();
|
|
324
|
-
const existing = before.users.find((user) => user.username === username);
|
|
325
|
-
const bootstrapCredential = before.bootstrapCredentialPresent
|
|
326
|
-
? parseBootstrapCredential(readFileSync(before.bootstrapCredentialPath, 'utf-8'))
|
|
327
|
-
: null;
|
|
328
|
-
|
|
329
|
-
if (existing) {
|
|
330
|
-
auth.rotatePassword(username, operation.password);
|
|
331
|
-
} else {
|
|
332
|
-
auth.addUser(username, operation.password, operation.roles ?? ['admin']);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
if (operation.retireBootstrapCredential) {
|
|
336
|
-
if (bootstrapCredential && bootstrapCredential.username !== username && auth.getUser(bootstrapCredential.username)) {
|
|
337
|
-
auth.deleteUser(bootstrapCredential.username);
|
|
338
|
-
}
|
|
339
|
-
auth.clearBootstrapCredentialFile();
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
if (operation.createSession ?? true) {
|
|
343
|
-
auth.createSession(username);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
return {
|
|
347
|
-
kind: operation.kind,
|
|
348
|
-
summary: existing
|
|
349
|
-
? `Updated local auth user ${username}.`
|
|
350
|
-
: `Created local auth user ${username}.`,
|
|
351
|
-
};
|
|
352
|
-
}
|
|
353
|
-
|
|
354
267
|
async function buildSecretRollbackAction(
|
|
355
268
|
deps: OnboardingApplyDependencies,
|
|
356
269
|
operation: Extract<OnboardingApplyOperation, { kind: 'set-secret' }>,
|
|
@@ -369,67 +282,6 @@ async function buildSecretRollbackAction(
|
|
|
369
282
|
};
|
|
370
283
|
}
|
|
371
284
|
|
|
372
|
-
function buildAuthRollbackAction(
|
|
373
|
-
deps: OnboardingApplyDependencies,
|
|
374
|
-
operation: Extract<OnboardingApplyOperation, { kind: 'ensure-auth-user' }>,
|
|
375
|
-
): RollbackAction {
|
|
376
|
-
validateAuthOperation(deps, operation);
|
|
377
|
-
const auth = deps.auth!;
|
|
378
|
-
const mutable = auth as unknown as MutableAuthManager;
|
|
379
|
-
const username = operation.username.trim();
|
|
380
|
-
const before = auth.inspect();
|
|
381
|
-
const existingUser = before.users.find((user) => user.username === username);
|
|
382
|
-
const existingSessionFingerprints = new Set(before.sessions
|
|
383
|
-
.filter((session) => session.username === username)
|
|
384
|
-
.map((session) => session.tokenFingerprint));
|
|
385
|
-
const userStoreSnapshot = existsSync(before.userStorePath) ? readFileSync(before.userStorePath, 'utf-8') : null;
|
|
386
|
-
const bootstrapCredentialSnapshot = existsSync(before.bootstrapCredentialPath)
|
|
387
|
-
? readFileSync(before.bootstrapCredentialPath, 'utf-8')
|
|
388
|
-
: null;
|
|
389
|
-
const bootstrapCredential = parseBootstrapCredential(bootstrapCredentialSnapshot);
|
|
390
|
-
const beforeSessions = mutable.sessions instanceof Map
|
|
391
|
-
? [...mutable.sessions.entries()].map(([token, session]) => [token, { ...session }] as const)
|
|
392
|
-
: [];
|
|
393
|
-
|
|
394
|
-
return () => {
|
|
395
|
-
for (const session of auth.inspect().sessions) {
|
|
396
|
-
if (session.username === username && !existingSessionFingerprints.has(session.tokenFingerprint)) {
|
|
397
|
-
auth.revokeSession(session.tokenFingerprint);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
if (bootstrapCredential && !auth.getUser(bootstrapCredential.username)) {
|
|
402
|
-
auth.addUser(bootstrapCredential.username, bootstrapCredential.password, ['admin']);
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
if (!existingUser && auth.getUser(username)) {
|
|
406
|
-
try {
|
|
407
|
-
auth.deleteUser(username);
|
|
408
|
-
} catch (error) {
|
|
409
|
-
if (mutable.users instanceof Map) mutable.users.delete(username);
|
|
410
|
-
else throw error;
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
restoreFile(before.bootstrapCredentialPath, bootstrapCredentialSnapshot);
|
|
415
|
-
restoreFile(before.userStorePath, userStoreSnapshot);
|
|
416
|
-
|
|
417
|
-
if (mutable.users instanceof Map) {
|
|
418
|
-
if (userStoreSnapshot === null) {
|
|
419
|
-
if (before.users.length === 0) mutable.users.clear();
|
|
420
|
-
} else {
|
|
421
|
-
mutable.users.clear();
|
|
422
|
-
for (const user of parsePersistedAuthUsers(userStoreSnapshot)) mutable.users.set(user.username, user);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
if (mutable.sessions instanceof Map) {
|
|
427
|
-
mutable.sessions.clear();
|
|
428
|
-
for (const [token, session] of beforeSessions) mutable.sessions.set(token, session);
|
|
429
|
-
}
|
|
430
|
-
};
|
|
431
|
-
}
|
|
432
|
-
|
|
433
285
|
async function buildRollbackAction(
|
|
434
286
|
deps: OnboardingApplyDependencies,
|
|
435
287
|
operation: OnboardingApplyOperation,
|
|
@@ -453,7 +305,8 @@ async function buildRollbackAction(
|
|
|
453
305
|
}
|
|
454
306
|
|
|
455
307
|
if (operation.kind === 'ensure-auth-user') {
|
|
456
|
-
|
|
308
|
+
validateAuthOperation(deps, operation);
|
|
309
|
+
return () => {};
|
|
457
310
|
}
|
|
458
311
|
|
|
459
312
|
if (operation.kind === 'acknowledge') {
|
|
@@ -633,8 +486,7 @@ export async function applyOnboardingRequest(
|
|
|
633
486
|
}
|
|
634
487
|
|
|
635
488
|
if (operation.kind === 'ensure-auth-user') {
|
|
636
|
-
|
|
637
|
-
rollbacks.push(rollback);
|
|
489
|
+
validateAuthOperation(deps, operation);
|
|
638
490
|
continue;
|
|
639
491
|
}
|
|
640
492
|
|
|
@@ -500,23 +500,23 @@ export function deriveReopenEditAcknowledgementState(
|
|
|
500
500
|
snapshot,
|
|
501
501
|
'auth',
|
|
502
502
|
'bootstrap-credential',
|
|
503
|
-
'
|
|
503
|
+
'An external runtime bootstrap credential signal is still visible to Agent.',
|
|
504
504
|
)
|
|
505
505
|
: authSessionCount > 0
|
|
506
506
|
? buildRequiredAcknowledgement(
|
|
507
507
|
snapshot,
|
|
508
508
|
'auth',
|
|
509
509
|
'active-sessions',
|
|
510
|
-
`${authSessionCount}
|
|
510
|
+
`${authSessionCount} external runtime auth session signal(s) are currently visible.`,
|
|
511
511
|
)
|
|
512
512
|
: authUserCount > 0
|
|
513
513
|
? buildRequiredAcknowledgement(
|
|
514
514
|
snapshot,
|
|
515
515
|
'auth',
|
|
516
516
|
'auth-state',
|
|
517
|
-
`${authUserCount}
|
|
517
|
+
`${authUserCount} external runtime auth user signal(s) are already visible.`,
|
|
518
518
|
)
|
|
519
|
-
: buildNotNeededAcknowledgement(snapshot, 'auth', 'No
|
|
519
|
+
: buildNotNeededAcknowledgement(snapshot, 'auth', 'No external runtime auth signal needs confirmation.');
|
|
520
520
|
|
|
521
521
|
return {
|
|
522
522
|
providers,
|
|
@@ -383,17 +383,7 @@ export interface OnboardingApplyDependencies {
|
|
|
383
383
|
readonly clock?: () => number;
|
|
384
384
|
readonly config: Pick<ConfigManager, 'get' | 'getRaw' | 'load' | 'setDynamic'>;
|
|
385
385
|
readonly secrets?: Pick<SecretsManager, 'delete' | 'get' | 'inspect' | 'set'>;
|
|
386
|
-
readonly auth?: Pick<
|
|
387
|
-
UserAuthManager,
|
|
388
|
-
'addUser'
|
|
389
|
-
| 'clearBootstrapCredentialFile'
|
|
390
|
-
| 'createSession'
|
|
391
|
-
| 'deleteUser'
|
|
392
|
-
| 'getUser'
|
|
393
|
-
| 'inspect'
|
|
394
|
-
| 'revokeSession'
|
|
395
|
-
| 'rotatePassword'
|
|
396
|
-
>;
|
|
386
|
+
readonly auth?: Pick<UserAuthManager, 'inspect'>;
|
|
397
387
|
readonly shellPaths: OnboardingShellPaths;
|
|
398
388
|
readonly acknowledgementScope?: OnboardingStateScope;
|
|
399
389
|
}
|
|
@@ -116,36 +116,14 @@ async function verifySecretOperation(
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
function verifyAuthOperation(
|
|
119
|
-
|
|
119
|
+
_deps: OnboardingVerificationDependencies,
|
|
120
120
|
operation: Extract<OnboardingApplyOperation, { kind: 'ensure-auth-user' }>,
|
|
121
121
|
): OnboardingVerificationItem {
|
|
122
|
-
if (!deps.auth) {
|
|
123
|
-
return {
|
|
124
|
-
id: `auth:${operation.username}`,
|
|
125
|
-
status: 'fail',
|
|
126
|
-
message: 'Local auth manager is unavailable.',
|
|
127
|
-
target: operation.username,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const snapshot = deps.auth.inspect();
|
|
132
122
|
const username = operation.username.trim();
|
|
133
|
-
const user = snapshot.users.find((entry) => entry.username === username);
|
|
134
|
-
const requiredRoles = operation.roles ?? ['admin'];
|
|
135
|
-
const userExists = Boolean(user) && requiredRoles.every((role) => user!.roles.includes(role));
|
|
136
|
-
const sessionExists = operation.createSession === false
|
|
137
|
-
? true
|
|
138
|
-
: snapshot.sessions.some((session) => session.username === username);
|
|
139
|
-
const bootstrapRetired = operation.retireBootstrapCredential
|
|
140
|
-
? snapshot.bootstrapCredentialPresent === false
|
|
141
|
-
: true;
|
|
142
|
-
const ok = userExists && sessionExists && bootstrapRetired;
|
|
143
123
|
return {
|
|
144
124
|
id: `auth:${username}`,
|
|
145
|
-
status:
|
|
146
|
-
message:
|
|
147
|
-
? `${username} local auth user has required role(s) and session state.`
|
|
148
|
-
: `${username} local auth user/session/role/bootstrap state was not created.`,
|
|
125
|
+
status: 'fail',
|
|
126
|
+
message: 'Runtime auth user/session administration is external to GoodVibes Agent onboarding.',
|
|
149
127
|
target: username,
|
|
150
128
|
};
|
|
151
129
|
}
|
package/src/version.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { join } from 'node:path';
|
|
|
6
6
|
// The prebuild script updates the fallback value before compilation.
|
|
7
7
|
// Uses import.meta.dir (Bun) to locate package.json relative to this file,
|
|
8
8
|
// which is correct regardless of the process working directory.
|
|
9
|
-
let _version = '0.1.
|
|
9
|
+
let _version = '0.1.82';
|
|
10
10
|
let _sdkVersion = '0.33.35';
|
|
11
11
|
try {
|
|
12
12
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8')) as {
|