@pellux/goodvibes-agent 0.1.56 → 0.1.58
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/.goodvibes/GOODVIBES.md +1 -1
- package/CHANGELOG.md +18 -9
- package/README.md +3 -3
- package/docs/README.md +1 -1
- package/docs/getting-started.md +3 -3
- package/docs/release-and-publishing.md +2 -2
- package/package.json +1 -3
- package/src/agent/routine-schedule-args.ts +219 -0
- package/src/agent/routine-schedule-format.ts +173 -0
- package/src/agent/routine-schedule-promotion.ts +3 -811
- package/src/agent/routine-schedule-receipts.ts +502 -0
- package/src/cli/agent-knowledge-command.ts +6 -6
- package/src/cli/help.ts +3 -25
- package/src/cli/package-verification.ts +23 -16
- package/src/cli/redaction.ts +4 -1
- package/src/cli/routines-command.ts +10 -6
- package/src/cli/service-posture.ts +47 -280
- package/src/cli/status.ts +0 -1
- package/src/cli/tui-startup.ts +23 -0
- package/src/config/secret-config.ts +0 -2
- package/src/input/agent-workspace-categories.ts +219 -0
- package/src/input/agent-workspace-editors.ts +143 -0
- package/src/input/agent-workspace-snapshot.ts +265 -0
- package/src/input/agent-workspace-types.ts +142 -0
- package/src/input/agent-workspace.ts +22 -766
- package/src/input/commands/agent-runtime-profile-runtime.ts +1 -1
- package/src/input/commands/delegation-runtime.ts +1 -1
- package/src/input/commands/experience-runtime.ts +3 -4
- package/src/input/commands/guidance-runtime.ts +1 -2
- package/src/input/commands/health-runtime.ts +3 -65
- package/src/input/commands/knowledge.ts +7 -7
- package/src/input/commands/local-setup-review.ts +0 -61
- package/src/input/commands/local-setup-transfer.ts +0 -3
- package/src/input/commands/local-setup.ts +2 -15
- package/src/input/commands/planning-runtime.ts +4 -1
- package/src/input/commands/platform-access-runtime.ts +1 -10
- package/src/input/commands/platform-services-runtime.ts +0 -1
- package/src/input/commands/recall-query.ts +1 -1
- package/src/input/commands/routines-runtime.ts +10 -6
- package/src/input/commands/schedule-runtime.ts +10 -6
- package/src/input/commands/session-workflow.ts +1 -1
- package/src/input/commands/tasks-runtime.ts +1 -14
- package/src/input/commands.ts +0 -4
- package/src/input/handler-onboarding.ts +10 -120
- package/src/input/onboarding/onboarding-wizard-apply.ts +5 -196
- package/src/input/onboarding/onboarding-wizard-constants.ts +8 -119
- package/src/input/onboarding/onboarding-wizard-helpers.ts +2 -53
- package/src/input/onboarding/onboarding-wizard-rules.ts +2 -236
- package/src/input/onboarding/onboarding-wizard-state.ts +1 -69
- package/src/input/onboarding/onboarding-wizard-steps.ts +584 -737
- package/src/input/onboarding/onboarding-wizard-types.ts +8 -26
- package/src/input/onboarding/onboarding-wizard.ts +4 -109
- package/src/input/settings-modal-agent-policy.ts +10 -0
- package/src/input/settings-modal-types.ts +2 -4
- package/src/input/settings-modal.ts +3 -1
- package/src/input/submission-router.ts +0 -1
- package/src/main.ts +13 -12
- package/src/panels/approval-panel.ts +1 -2
- package/src/panels/builtin/operations.ts +1 -2
- package/src/panels/knowledge-panel.ts +2 -2
- package/src/panels/project-planning-panel.ts +4 -1
- package/src/panels/provider-health-domains.ts +0 -22
- package/src/panels/provider-health-panel.ts +1 -5
- package/src/panels/session-browser-panel.ts +0 -5
- package/src/panels/tasks-panel.ts +2 -64
- package/src/renderer/agent-workspace.ts +1 -1
- package/src/renderer/help-overlay.ts +1 -2
- package/src/renderer/semantic-diff.ts +1 -1
- package/src/renderer/settings-modal-helpers.ts +0 -16
- package/src/renderer/settings-modal.ts +3 -5
- package/src/runtime/bootstrap-hook-bridge.ts +0 -3
- package/src/runtime/bootstrap-shell.ts +2 -1
- package/src/runtime/bootstrap.ts +1 -1
- package/src/runtime/index.ts +0 -1
- package/src/runtime/onboarding/derivation.ts +1 -28
- package/src/runtime/onboarding/snapshot.ts +0 -1
- package/src/runtime/onboarding/types.ts +1 -4
- package/src/runtime/services.ts +4 -23
- package/src/runtime/ui-read-models.ts +4 -3
- package/src/shell/service-settings-sync.ts +15 -244
- package/src/tools/agent-context-policy.ts +1 -1
- package/src/tools/wrfc-agent-guard.ts +3 -3
- package/src/verification/live-verifier.ts +11 -5
- package/src/verification/verification-ledger.ts +3 -6
- package/src/version.ts +1 -1
- package/src/input/commands/agent-externalized-tui.ts +0 -73
- package/src/input/commands/cloudflare-runtime.ts +0 -385
- package/src/input/handler-onboarding-cloudflare.ts +0 -322
- package/src/input/onboarding/onboarding-runtime-status.ts +0 -87
- package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +0 -494
- package/src/input/onboarding/onboarding-wizard-cloudflare.ts +0 -199
- package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +0 -130
- package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +0 -762
- package/src/runtime/cloudflare-control-plane.ts +0 -350
- package/src/runtime/sandbox-public-gaps.ts +0 -358
|
@@ -1,350 +0,0 @@
|
|
|
1
|
-
import { join } from 'node:path';
|
|
2
|
-
import { getOrCreateCompanionToken } from '@pellux/goodvibes-sdk/platform/pairing';
|
|
3
|
-
import type { ConfigManager } from '../config/index.ts';
|
|
4
|
-
import { GOODVIBES_AGENT_PAIRING_SURFACE } from '../config/surface.ts';
|
|
5
|
-
|
|
6
|
-
export const CLOUDFLARE_COMPONENT_IDS = [
|
|
7
|
-
'workers',
|
|
8
|
-
'queues',
|
|
9
|
-
'zeroTrustTunnel',
|
|
10
|
-
'zeroTrustAccess',
|
|
11
|
-
'dns',
|
|
12
|
-
'kv',
|
|
13
|
-
'durableObjects',
|
|
14
|
-
'secretsStore',
|
|
15
|
-
'r2',
|
|
16
|
-
] as const;
|
|
17
|
-
|
|
18
|
-
export type CloudflareComponent = typeof CLOUDFLARE_COMPONENT_IDS[number];
|
|
19
|
-
export type CloudflareComponentSelection = Partial<Record<CloudflareComponent, boolean>>;
|
|
20
|
-
export type CloudflareBatchMode = 'off' | 'explicit' | 'eligible-by-default';
|
|
21
|
-
|
|
22
|
-
export const DEFAULT_CLOUDFLARE_COMPONENT_SELECTION: Readonly<Record<CloudflareComponent, boolean>> = {
|
|
23
|
-
workers: true,
|
|
24
|
-
queues: true,
|
|
25
|
-
zeroTrustTunnel: false,
|
|
26
|
-
zeroTrustAccess: false,
|
|
27
|
-
dns: false,
|
|
28
|
-
kv: false,
|
|
29
|
-
durableObjects: false,
|
|
30
|
-
secretsStore: false,
|
|
31
|
-
r2: false,
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export const CLOUDFLARE_COMPONENT_LABELS: Readonly<Record<CloudflareComponent, string>> = {
|
|
35
|
-
workers: 'Workers',
|
|
36
|
-
queues: 'Queues',
|
|
37
|
-
zeroTrustTunnel: 'Zero Trust Tunnel',
|
|
38
|
-
zeroTrustAccess: 'Zero Trust Access',
|
|
39
|
-
dns: 'DNS hostname',
|
|
40
|
-
kv: 'KV',
|
|
41
|
-
durableObjects: 'Durable Objects',
|
|
42
|
-
secretsStore: 'Secrets Store',
|
|
43
|
-
r2: 'R2 artifacts',
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export interface CloudflareProvisionStep {
|
|
47
|
-
readonly name: string;
|
|
48
|
-
readonly status: 'ok' | 'skipped' | 'warning';
|
|
49
|
-
readonly message?: string;
|
|
50
|
-
readonly resourceId?: string;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface CloudflareControlPlaneStatus {
|
|
54
|
-
readonly enabled: boolean;
|
|
55
|
-
readonly ready: boolean;
|
|
56
|
-
readonly configured: Record<string, boolean>;
|
|
57
|
-
readonly config: Record<string, unknown>;
|
|
58
|
-
readonly warnings: readonly string[];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export interface CloudflareTokenRequirement {
|
|
62
|
-
readonly component: CloudflareComponent | 'bootstrap';
|
|
63
|
-
readonly scope: 'account' | 'zone' | 'user' | 'r2';
|
|
64
|
-
readonly permission: string;
|
|
65
|
-
readonly alternatives?: readonly string[];
|
|
66
|
-
readonly reason: string;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface CloudflareTokenRequirementsResult {
|
|
70
|
-
readonly ok: true;
|
|
71
|
-
readonly components: Readonly<Record<CloudflareComponent, boolean>>;
|
|
72
|
-
readonly permissions: readonly CloudflareTokenRequirement[];
|
|
73
|
-
readonly bootstrapToken: {
|
|
74
|
-
readonly requiredForSdkCreation: boolean;
|
|
75
|
-
readonly storeInGoodVibes: false;
|
|
76
|
-
readonly instructions: readonly string[];
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export interface CloudflareValidateResult {
|
|
81
|
-
readonly ok: boolean;
|
|
82
|
-
readonly account?: {
|
|
83
|
-
readonly id: string;
|
|
84
|
-
readonly name: string;
|
|
85
|
-
readonly type?: string;
|
|
86
|
-
};
|
|
87
|
-
readonly tokenSource: string;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export interface CloudflareOperationalTokenResult {
|
|
91
|
-
readonly ok: true;
|
|
92
|
-
readonly tokenId?: string;
|
|
93
|
-
readonly tokenName: string;
|
|
94
|
-
readonly tokenSource: 'bootstrap';
|
|
95
|
-
readonly apiTokenRef?: string;
|
|
96
|
-
readonly generatedToken?: string;
|
|
97
|
-
readonly accountId: string;
|
|
98
|
-
readonly zoneId?: string;
|
|
99
|
-
readonly permissions: readonly CloudflareTokenRequirement[];
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export interface CloudflareDiscoverResult {
|
|
103
|
-
readonly ok: true;
|
|
104
|
-
readonly tokenSource: string;
|
|
105
|
-
readonly accounts: ReadonlyArray<{ readonly id: string; readonly name: string; readonly type?: string }>;
|
|
106
|
-
readonly selectedAccount?: { readonly id: string; readonly name: string; readonly type?: string };
|
|
107
|
-
readonly zones: ReadonlyArray<{ readonly id: string; readonly name: string; readonly status?: string; readonly type?: string }>;
|
|
108
|
-
readonly selectedZone?: { readonly id: string; readonly name: string; readonly status?: string; readonly type?: string };
|
|
109
|
-
readonly workerSubdomain?: string;
|
|
110
|
-
readonly queues?: ReadonlyArray<{ readonly queue_id?: string; readonly queue_name?: string }>;
|
|
111
|
-
readonly kvNamespaces?: ReadonlyArray<{ readonly id?: string; readonly title?: string }>;
|
|
112
|
-
readonly durableObjectNamespaces?: ReadonlyArray<{ readonly id?: string; readonly name?: string; readonly class?: string }>;
|
|
113
|
-
readonly r2Buckets?: ReadonlyArray<{ readonly name?: string; readonly storage_class?: string }>;
|
|
114
|
-
readonly secretsStores?: ReadonlyArray<{ readonly id: string; readonly name: string }>;
|
|
115
|
-
readonly tunnels?: ReadonlyArray<{ readonly id?: string; readonly name?: string; readonly status?: string }>;
|
|
116
|
-
readonly accessApplications?: ReadonlyArray<{ readonly id?: string; readonly name?: string; readonly domain?: string; readonly type?: string }>;
|
|
117
|
-
readonly warnings: readonly string[];
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export interface CloudflareProvisionResult {
|
|
121
|
-
readonly ok: boolean;
|
|
122
|
-
readonly dryRun: false;
|
|
123
|
-
readonly steps: readonly CloudflareProvisionStep[];
|
|
124
|
-
readonly account?: { readonly id: string; readonly name: string };
|
|
125
|
-
readonly worker?: { readonly name: string; readonly baseUrl?: string; readonly subdomain?: string; readonly hostname?: string; readonly cron?: string };
|
|
126
|
-
readonly queues?: { readonly queueName: string; readonly queueId: string; readonly deadLetterQueueName: string; readonly deadLetterQueueId: string; readonly consumerId?: string };
|
|
127
|
-
readonly tunnel?: { readonly id: string; readonly name: string; readonly hostname?: string; readonly tokenRef?: string };
|
|
128
|
-
readonly access?: { readonly appId?: string; readonly serviceTokenId?: string; readonly serviceTokenRef?: string };
|
|
129
|
-
readonly dns?: {
|
|
130
|
-
readonly zoneId: string;
|
|
131
|
-
readonly zoneName?: string;
|
|
132
|
-
readonly records: ReadonlyArray<{ readonly id?: string; readonly name?: string; readonly type?: string; readonly content?: string }>;
|
|
133
|
-
};
|
|
134
|
-
readonly kv?: { readonly namespaceName: string; readonly namespaceId: string };
|
|
135
|
-
readonly durableObjects?: { readonly namespaceName: string; readonly namespaceId?: string };
|
|
136
|
-
readonly r2?: { readonly bucketName: string; readonly storageClass: 'Standard' };
|
|
137
|
-
readonly secretsStore?: { readonly storeName: string; readonly storeId: string };
|
|
138
|
-
readonly verification?: CloudflareVerifyResult;
|
|
139
|
-
readonly generatedSecrets?: {
|
|
140
|
-
readonly workerClientToken?: string;
|
|
141
|
-
readonly tunnelToken?: string;
|
|
142
|
-
readonly accessServiceTokenClientId?: string;
|
|
143
|
-
readonly accessServiceTokenClientSecret?: string;
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export interface CloudflareVerifyResult {
|
|
148
|
-
readonly ok: boolean;
|
|
149
|
-
readonly workerHealth: {
|
|
150
|
-
readonly ok: boolean;
|
|
151
|
-
readonly status: number;
|
|
152
|
-
readonly error?: string;
|
|
153
|
-
};
|
|
154
|
-
readonly daemonBatchProxy?: {
|
|
155
|
-
readonly ok: boolean;
|
|
156
|
-
readonly status: number;
|
|
157
|
-
readonly error?: string;
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export interface CloudflareDisableResult {
|
|
162
|
-
readonly ok: boolean;
|
|
163
|
-
readonly steps: readonly CloudflareProvisionStep[];
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export interface CloudflareTokenRequirementsRequest {
|
|
167
|
-
readonly components?: CloudflareComponentSelection;
|
|
168
|
-
readonly includeBootstrap?: boolean;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
export interface CloudflareOperationalTokenRequest extends CloudflareTokenRequirementsRequest {
|
|
172
|
-
readonly accountId?: string;
|
|
173
|
-
readonly zoneId?: string;
|
|
174
|
-
readonly zoneName?: string;
|
|
175
|
-
readonly bootstrapToken?: string;
|
|
176
|
-
readonly tokenName?: string;
|
|
177
|
-
readonly expiresOn?: string;
|
|
178
|
-
readonly persistConfig?: boolean;
|
|
179
|
-
readonly storeApiToken?: boolean;
|
|
180
|
-
readonly returnGeneratedToken?: boolean;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export interface CloudflareValidateRequest {
|
|
184
|
-
readonly accountId?: string;
|
|
185
|
-
readonly apiToken?: string;
|
|
186
|
-
readonly apiTokenRef?: string;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export interface CloudflareDiscoverRequest extends CloudflareValidateRequest {
|
|
190
|
-
readonly components?: CloudflareComponentSelection;
|
|
191
|
-
readonly zoneId?: string;
|
|
192
|
-
readonly zoneName?: string;
|
|
193
|
-
readonly includeResources?: boolean;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
export interface CloudflareProvisionRequest extends CloudflareDiscoverRequest {
|
|
197
|
-
readonly workerName?: string;
|
|
198
|
-
readonly workerSubdomain?: string;
|
|
199
|
-
readonly workerHostname?: string;
|
|
200
|
-
readonly workerBaseUrl?: string;
|
|
201
|
-
readonly daemonBaseUrl?: string;
|
|
202
|
-
readonly daemonHostname?: string;
|
|
203
|
-
readonly queueName?: string;
|
|
204
|
-
readonly deadLetterQueueName?: string;
|
|
205
|
-
readonly tunnelName?: string;
|
|
206
|
-
readonly tunnelId?: string;
|
|
207
|
-
readonly tunnelServiceUrl?: string;
|
|
208
|
-
readonly tunnelTokenRef?: string;
|
|
209
|
-
readonly accessAppId?: string;
|
|
210
|
-
readonly accessServiceTokenId?: string;
|
|
211
|
-
readonly accessServiceTokenRef?: string;
|
|
212
|
-
readonly kvNamespaceName?: string;
|
|
213
|
-
readonly kvNamespaceId?: string;
|
|
214
|
-
readonly durableObjectNamespaceName?: string;
|
|
215
|
-
readonly durableObjectNamespaceId?: string;
|
|
216
|
-
readonly r2BucketName?: string;
|
|
217
|
-
readonly secretsStoreName?: string;
|
|
218
|
-
readonly secretsStoreId?: string;
|
|
219
|
-
readonly workerCron?: string;
|
|
220
|
-
readonly operatorToken?: string;
|
|
221
|
-
readonly operatorTokenRef?: string;
|
|
222
|
-
readonly workerClientToken?: string;
|
|
223
|
-
readonly workerClientTokenRef?: string;
|
|
224
|
-
readonly storeApiToken?: boolean;
|
|
225
|
-
readonly storeOperatorToken?: boolean;
|
|
226
|
-
readonly storeWorkerClientToken?: boolean;
|
|
227
|
-
readonly returnGeneratedSecrets?: boolean;
|
|
228
|
-
readonly enableWorkersDev?: boolean;
|
|
229
|
-
readonly queueJobPayloads?: boolean;
|
|
230
|
-
readonly verify?: boolean;
|
|
231
|
-
readonly persistConfig?: boolean;
|
|
232
|
-
readonly batchMode?: CloudflareBatchMode;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
export interface CloudflareVerifyRequest {
|
|
236
|
-
readonly workerBaseUrl?: string;
|
|
237
|
-
readonly workerClientToken?: string;
|
|
238
|
-
readonly workerClientTokenRef?: string;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
export interface CloudflareDisableRequest extends CloudflareValidateRequest {
|
|
242
|
-
readonly workerName?: string;
|
|
243
|
-
readonly disableWorkerSubdomain?: boolean;
|
|
244
|
-
readonly disableCron?: boolean;
|
|
245
|
-
readonly persistConfig?: boolean;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
export class CloudflareDaemonRouteError extends Error {
|
|
249
|
-
readonly status: number;
|
|
250
|
-
readonly code: string;
|
|
251
|
-
|
|
252
|
-
constructor(message: string, status: number, code: string) {
|
|
253
|
-
super(message);
|
|
254
|
-
this.name = 'CloudflareDaemonRouteError';
|
|
255
|
-
this.status = status;
|
|
256
|
-
this.code = code;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
export interface CloudflareDaemonClient {
|
|
261
|
-
status(): Promise<CloudflareControlPlaneStatus>;
|
|
262
|
-
tokenRequirements(input?: CloudflareTokenRequirementsRequest): Promise<CloudflareTokenRequirementsResult>;
|
|
263
|
-
createOperationalToken(input: CloudflareOperationalTokenRequest): Promise<CloudflareOperationalTokenResult>;
|
|
264
|
-
discover(input?: CloudflareDiscoverRequest): Promise<CloudflareDiscoverResult>;
|
|
265
|
-
validate(input?: CloudflareValidateRequest): Promise<CloudflareValidateResult>;
|
|
266
|
-
provision(input: CloudflareProvisionRequest): Promise<CloudflareProvisionResult>;
|
|
267
|
-
verify(input?: CloudflareVerifyRequest): Promise<CloudflareVerifyResult>;
|
|
268
|
-
disable(input?: CloudflareDisableRequest): Promise<CloudflareDisableResult>;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
export interface CloudflareDaemonClientOptions {
|
|
272
|
-
readonly configManager: Pick<ConfigManager, 'get'>;
|
|
273
|
-
readonly homeDirectory: string;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function connectHostForBindHost(host: string): string {
|
|
277
|
-
if (host === '0.0.0.0' || host === '::' || host.trim().length === 0) return '127.0.0.1';
|
|
278
|
-
return host;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function hostForUrl(host: string): string {
|
|
282
|
-
return host.includes(':') && !host.startsWith('[') ? `[${host}]` : host;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
export function resolveCloudflareDaemonBaseUrl(configManager: Pick<ConfigManager, 'get'>): string {
|
|
286
|
-
const configuredBaseUrl = String(configManager.get('controlPlane.baseUrl' as never) ?? '').trim();
|
|
287
|
-
if (configuredBaseUrl) return configuredBaseUrl.replace(/\/+$/, '');
|
|
288
|
-
const host = hostForUrl(connectHostForBindHost(String(configManager.get('controlPlane.host' as never) ?? '127.0.0.1')));
|
|
289
|
-
const portValue = Number(configManager.get('controlPlane.port' as never) ?? 3421);
|
|
290
|
-
const port = Number.isFinite(portValue) && portValue > 0 ? portValue : 3421;
|
|
291
|
-
return `http://${host}:${port}`;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
export function buildDefaultCloudflareDaemonBaseUrl(configManager: Pick<ConfigManager, 'get'>): string {
|
|
295
|
-
return resolveCloudflareDaemonBaseUrl(configManager);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function readDaemonToken(homeDirectory: string): string {
|
|
299
|
-
const daemonHomeDir = join(homeDirectory, '.goodvibes', 'daemon');
|
|
300
|
-
return getOrCreateCompanionToken(GOODVIBES_AGENT_PAIRING_SURFACE, { daemonHomeDir }).token;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
async function readJsonResponse<T>(response: Response): Promise<T> {
|
|
304
|
-
const text = await response.text();
|
|
305
|
-
const body = text.trim().length > 0 ? JSON.parse(text) as unknown : {};
|
|
306
|
-
if (!response.ok) {
|
|
307
|
-
const record = body && typeof body === 'object' ? body as Record<string, unknown> : {};
|
|
308
|
-
const message = typeof record.error === 'string' ? record.error : `Cloudflare daemon route failed with HTTP ${response.status}`;
|
|
309
|
-
const code = typeof record.code === 'string' ? record.code : 'CLOUDFLARE_DAEMON_ROUTE_ERROR';
|
|
310
|
-
throw new CloudflareDaemonRouteError(message, response.status, code);
|
|
311
|
-
}
|
|
312
|
-
return body as T;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
export function createCloudflareDaemonClient(options: CloudflareDaemonClientOptions): CloudflareDaemonClient {
|
|
316
|
-
const baseUrl = resolveCloudflareDaemonBaseUrl(options.configManager);
|
|
317
|
-
const token = readDaemonToken(options.homeDirectory);
|
|
318
|
-
|
|
319
|
-
const requestJson = async <T>(path: string, init: RequestInit = {}): Promise<T> => {
|
|
320
|
-
const headers = new Headers(init.headers);
|
|
321
|
-
headers.set('Authorization', `Bearer ${token}`);
|
|
322
|
-
if (init.body !== undefined) headers.set('Content-Type', 'application/json');
|
|
323
|
-
const response = await fetch(`${baseUrl}${path}`, { ...init, headers });
|
|
324
|
-
return await readJsonResponse<T>(response);
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
const postJson = <T>(path: string, body: unknown): Promise<T> => requestJson<T>(path, {
|
|
328
|
-
method: 'POST',
|
|
329
|
-
body: JSON.stringify(body ?? {}),
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
return {
|
|
333
|
-
status: () => requestJson<CloudflareControlPlaneStatus>('/api/cloudflare/status'),
|
|
334
|
-
tokenRequirements: (input = {}) => postJson<CloudflareTokenRequirementsResult>('/api/cloudflare/token/requirements', input),
|
|
335
|
-
createOperationalToken: (input) => postJson<CloudflareOperationalTokenResult>('/api/cloudflare/token/create', input),
|
|
336
|
-
discover: (input = {}) => postJson<CloudflareDiscoverResult>('/api/cloudflare/discover', input),
|
|
337
|
-
validate: (input = {}) => postJson<CloudflareValidateResult>('/api/cloudflare/validate', input),
|
|
338
|
-
provision: (input) => postJson<CloudflareProvisionResult>('/api/cloudflare/provision', input),
|
|
339
|
-
verify: (input = {}) => postJson<CloudflareVerifyResult>('/api/cloudflare/verify', input),
|
|
340
|
-
disable: (input = {}) => postJson<CloudflareDisableResult>('/api/cloudflare/disable', input),
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
export function normalizeCloudflareComponents(selection: CloudflareComponentSelection | undefined): Record<CloudflareComponent, boolean> {
|
|
345
|
-
const result: Record<CloudflareComponent, boolean> = { ...DEFAULT_CLOUDFLARE_COMPONENT_SELECTION };
|
|
346
|
-
for (const component of CLOUDFLARE_COMPONENT_IDS) {
|
|
347
|
-
if (typeof selection?.[component] === 'boolean') result[component] = selection[component] === true;
|
|
348
|
-
}
|
|
349
|
-
return result;
|
|
350
|
-
}
|
|
@@ -1,358 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
-
import { resolve } from 'node:path';
|
|
3
|
-
import { spawnSync } from 'node:child_process';
|
|
4
|
-
import {
|
|
5
|
-
detectSandboxHostStatus,
|
|
6
|
-
getSandboxConfigSnapshot,
|
|
7
|
-
type ConfigManagerLike,
|
|
8
|
-
type SandboxBackendProbe,
|
|
9
|
-
type SandboxLaunchPlan,
|
|
10
|
-
type SandboxProfile,
|
|
11
|
-
} from '@pellux/goodvibes-sdk/platform/runtime/sandbox';
|
|
12
|
-
export interface SandboxGuestBundle {
|
|
13
|
-
readonly version: 1;
|
|
14
|
-
readonly exportedAt: number;
|
|
15
|
-
readonly guest: {
|
|
16
|
-
readonly qemuBinary: string;
|
|
17
|
-
readonly imagePath: string;
|
|
18
|
-
readonly wrapperPath: string;
|
|
19
|
-
readonly host: string;
|
|
20
|
-
readonly port: number;
|
|
21
|
-
readonly user: string;
|
|
22
|
-
readonly workspacePath: string;
|
|
23
|
-
readonly sessionMode: string;
|
|
24
|
-
readonly replJavaScriptCommand: string;
|
|
25
|
-
};
|
|
26
|
-
readonly nextSteps: readonly string[];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface SandboxQemuInitBundle {
|
|
30
|
-
readonly directory: string;
|
|
31
|
-
readonly wrapperPath: string;
|
|
32
|
-
readonly guestBundlePath: string;
|
|
33
|
-
readonly readmePath: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface SandboxQemuSetupBundle extends SandboxQemuInitBundle {
|
|
37
|
-
readonly imagePath: string;
|
|
38
|
-
readonly imageCreateScriptPath: string;
|
|
39
|
-
readonly guestBootstrapScriptPath: string;
|
|
40
|
-
readonly projectionPolicyPath: string;
|
|
41
|
-
readonly sshConfigPath: string;
|
|
42
|
-
readonly seedDirectory: string;
|
|
43
|
-
readonly seedIsoPath: string;
|
|
44
|
-
readonly sshKeyPath: string;
|
|
45
|
-
readonly sshPublicKeyPath: string;
|
|
46
|
-
readonly manifestPath: string;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export interface SandboxQemuSetupManifest {
|
|
50
|
-
readonly version: 1;
|
|
51
|
-
readonly createdAt: number;
|
|
52
|
-
readonly wrapperPath: string;
|
|
53
|
-
readonly imagePath: string;
|
|
54
|
-
readonly imageCreateScriptPath: string;
|
|
55
|
-
readonly guestBootstrapScriptPath: string;
|
|
56
|
-
readonly projectionPolicyPath: string;
|
|
57
|
-
readonly sshConfigPath: string;
|
|
58
|
-
readonly seedDirectory: string;
|
|
59
|
-
readonly seedIsoPath: string;
|
|
60
|
-
readonly sshKeyPath: string;
|
|
61
|
-
readonly sshPublicKeyPath: string;
|
|
62
|
-
readonly recommendedSettings: {
|
|
63
|
-
readonly backend: 'qemu';
|
|
64
|
-
readonly qemuBinary: string;
|
|
65
|
-
readonly wrapperPath: string;
|
|
66
|
-
readonly imagePath: string;
|
|
67
|
-
readonly guestHost: string;
|
|
68
|
-
readonly guestPort: number;
|
|
69
|
-
readonly guestUser: string;
|
|
70
|
-
readonly guestWorkspacePath: string;
|
|
71
|
-
readonly sessionMode: string;
|
|
72
|
-
readonly replJavaScriptCommand: string;
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export interface SandboxProvisioningOptions {
|
|
77
|
-
readonly surfaceRoot: string;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export interface WritableConfigManagerLike extends ConfigManagerLike {
|
|
81
|
-
setDynamic(key: string, value: unknown): void;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export class AgentSandboxExternalizedError extends Error {
|
|
85
|
-
readonly code = 'sandbox_externalized_to_tui';
|
|
86
|
-
readonly action: string;
|
|
87
|
-
|
|
88
|
-
constructor(action: string) {
|
|
89
|
-
super(`Agent sandbox ${action} is externalized to GoodVibes TUI. Delegate build/sandbox/QEMU work to a GoodVibes TUI session instead.`);
|
|
90
|
-
this.name = 'AgentSandboxExternalizedError';
|
|
91
|
-
this.action = action;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function sandboxExternalized(action: string): never {
|
|
96
|
-
throw new AgentSandboxExternalizedError(action);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export function probeSandboxBackends(manager: ConfigManagerLike): SandboxBackendProbe {
|
|
100
|
-
const host = detectSandboxHostStatus(manager);
|
|
101
|
-
const config = getSandboxConfigSnapshot(manager);
|
|
102
|
-
const qemuBinary = config.qemuBinary || 'qemu-system-x86_64';
|
|
103
|
-
const qemuImage = config.qemuImagePath || '';
|
|
104
|
-
const qemuExecWrapper = config.qemuExecWrapper || '';
|
|
105
|
-
const qemuGuestHost = config.qemuGuestHost || '';
|
|
106
|
-
const qemuProbe = spawnSync(qemuBinary, ['--version'], {
|
|
107
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
108
|
-
encoding: 'buffer',
|
|
109
|
-
timeout: 1500,
|
|
110
|
-
windowsHide: true,
|
|
111
|
-
});
|
|
112
|
-
const qemuAvailable = qemuProbe.status === 0 || qemuProbe.status === 1;
|
|
113
|
-
const qemuBlockedReason = host.windows && !host.runningInWsl
|
|
114
|
-
? 'QEMU sandboxing on Windows requires running GoodVibes inside WSL.'
|
|
115
|
-
: '';
|
|
116
|
-
const qemuDetail = qemuBlockedReason || (
|
|
117
|
-
qemuAvailable
|
|
118
|
-
? `requires ${qemuBinary} on PATH`
|
|
119
|
-
: `requires ${qemuBinary} on PATH (${qemuProbe.error instanceof Error ? qemuProbe.error.message : `exit ${qemuProbe.status ?? 'unknown'}`})`
|
|
120
|
-
);
|
|
121
|
-
const warnings: string[] = [];
|
|
122
|
-
if (config.vmBackend === 'qemu' && !qemuAvailable) {
|
|
123
|
-
warnings.push(`Requested sandbox backend "${config.vmBackend}" is unavailable; local process isolation will not be used unless sandbox.vmBackend is set to "local".`);
|
|
124
|
-
}
|
|
125
|
-
if (config.vmBackend === 'qemu' && !qemuImage) {
|
|
126
|
-
warnings.push('QEMU backend selected without sandbox.qemuImagePath; sessions can be planned and reviewed, but guest execution remains disabled.');
|
|
127
|
-
}
|
|
128
|
-
if (config.vmBackend === 'qemu' && qemuImage && !qemuExecWrapper) {
|
|
129
|
-
warnings.push('QEMU image is configured without sandbox.qemuExecWrapper; guest launch planning is wired, but command execution remains disabled until a host bridge is configured.');
|
|
130
|
-
}
|
|
131
|
-
if (config.vmBackend === 'qemu' && qemuExecWrapper && !qemuGuestHost) {
|
|
132
|
-
warnings.push('QEMU wrapper is configured without sandbox.qemuGuestHost; host bridge mode is available, but real guest SSH transport remains disabled until the guest host is configured.');
|
|
133
|
-
}
|
|
134
|
-
if (config.vmBackend === 'qemu' && qemuExecWrapper && !existsSync(qemuExecWrapper)) {
|
|
135
|
-
warnings.push(`Configured sandbox.qemuExecWrapper does not exist: ${qemuExecWrapper}`);
|
|
136
|
-
}
|
|
137
|
-
if (config.vmBackend === 'qemu' && qemuExecWrapper && existsSync(qemuExecWrapper) && !isExecutableFile(qemuExecWrapper)) {
|
|
138
|
-
warnings.push(`Configured sandbox.qemuExecWrapper is not executable: ${qemuExecWrapper}`);
|
|
139
|
-
}
|
|
140
|
-
if (qemuBlockedReason) warnings.push(qemuBlockedReason);
|
|
141
|
-
return {
|
|
142
|
-
requestedBackend: config.vmBackend,
|
|
143
|
-
resolvedBackend: config.vmBackend,
|
|
144
|
-
backends: [
|
|
145
|
-
{ id: 'local', available: true, detail: 'host-local process isolation is available when explicitly selected' },
|
|
146
|
-
{ id: 'qemu', available: qemuAvailable && !qemuBlockedReason, detail: qemuDetail },
|
|
147
|
-
],
|
|
148
|
-
warnings,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export function buildSandboxLaunchPlan(
|
|
153
|
-
profile: SandboxProfile,
|
|
154
|
-
label: string,
|
|
155
|
-
manager: ConfigManagerLike,
|
|
156
|
-
workspaceRoot: string,
|
|
157
|
-
): SandboxLaunchPlan {
|
|
158
|
-
const config = getSandboxConfigSnapshot(manager);
|
|
159
|
-
const backendProbe = probeSandboxBackends(manager);
|
|
160
|
-
const backend = backendProbe.resolvedBackend === 'qemu' ? 'qemu' : 'local';
|
|
161
|
-
const safeWorkspaceRoot = resolve(workspaceRoot);
|
|
162
|
-
if (backend === 'qemu') {
|
|
163
|
-
void profile;
|
|
164
|
-
void label;
|
|
165
|
-
void config;
|
|
166
|
-
void safeWorkspaceRoot;
|
|
167
|
-
sandboxExternalized('QEMU launch planning');
|
|
168
|
-
}
|
|
169
|
-
return {
|
|
170
|
-
backend: 'local',
|
|
171
|
-
command: process.env.SHELL || 'bash',
|
|
172
|
-
args: ['-lc', `echo "goodvibes sandbox ${profile.id}: ${label}"`],
|
|
173
|
-
workspaceRoot: safeWorkspaceRoot,
|
|
174
|
-
summary: buildCommandSummary(process.env.SHELL || 'bash', ['-lc', `echo "goodvibes sandbox ${profile.id}: ${label}"`]),
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
export interface SandboxCommandPlan {
|
|
179
|
-
readonly command: string;
|
|
180
|
-
readonly args: readonly string[];
|
|
181
|
-
readonly summary: string;
|
|
182
|
-
readonly env?: NodeJS.ProcessEnv | undefined;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export interface SandboxCommandResult {
|
|
186
|
-
readonly status: number | null;
|
|
187
|
-
readonly stdout: string;
|
|
188
|
-
readonly stderr: string;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function buildCommandSummary(command: string, args: readonly string[]): string {
|
|
192
|
-
return [command, ...args].join(' ').trim();
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function isExecutableFile(path: string): boolean {
|
|
196
|
-
try {
|
|
197
|
-
const stat = statSync(path);
|
|
198
|
-
return stat.isFile() && (stat.mode & 0o111) !== 0;
|
|
199
|
-
} catch {
|
|
200
|
-
return false;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export function resolveSandboxCommandPlan(
|
|
205
|
-
launchPlan: SandboxLaunchPlan,
|
|
206
|
-
command: string,
|
|
207
|
-
args: readonly string[],
|
|
208
|
-
manager?: ConfigManagerLike,
|
|
209
|
-
): SandboxCommandPlan {
|
|
210
|
-
if (launchPlan.backend === 'qemu') {
|
|
211
|
-
void manager;
|
|
212
|
-
sandboxExternalized('QEMU command planning');
|
|
213
|
-
}
|
|
214
|
-
return {
|
|
215
|
-
command,
|
|
216
|
-
args,
|
|
217
|
-
summary: buildCommandSummary(command, args),
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
export function executeSandboxCommand(
|
|
222
|
-
launchPlan: SandboxLaunchPlan,
|
|
223
|
-
command: string,
|
|
224
|
-
args: readonly string[],
|
|
225
|
-
options: {
|
|
226
|
-
readonly cwd?: string;
|
|
227
|
-
readonly env?: NodeJS.ProcessEnv;
|
|
228
|
-
readonly inheritHostEnv?: boolean;
|
|
229
|
-
readonly timeoutMs?: number;
|
|
230
|
-
readonly input?: string;
|
|
231
|
-
} = {},
|
|
232
|
-
): SandboxCommandResult {
|
|
233
|
-
void launchPlan;
|
|
234
|
-
void command;
|
|
235
|
-
void args;
|
|
236
|
-
void options;
|
|
237
|
-
sandboxExternalized('command execution');
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
export function executeSandboxManagedCommand(
|
|
241
|
-
launchPlan: SandboxLaunchPlan,
|
|
242
|
-
command: string,
|
|
243
|
-
args: readonly string[],
|
|
244
|
-
manager: ConfigManagerLike,
|
|
245
|
-
options: {
|
|
246
|
-
readonly cwd?: string;
|
|
247
|
-
readonly env?: NodeJS.ProcessEnv;
|
|
248
|
-
readonly inheritHostEnv?: boolean;
|
|
249
|
-
readonly timeoutMs?: number;
|
|
250
|
-
readonly input?: string;
|
|
251
|
-
} = {},
|
|
252
|
-
): SandboxCommandResult {
|
|
253
|
-
void launchPlan;
|
|
254
|
-
void command;
|
|
255
|
-
void args;
|
|
256
|
-
void manager;
|
|
257
|
-
void options;
|
|
258
|
-
sandboxExternalized('managed command execution');
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function resolveWorkspacePath(workspaceRoot: string, pathArg: string): string {
|
|
262
|
-
return resolve(workspaceRoot, pathArg);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
export function scaffoldSandboxQemuInitBundle(
|
|
266
|
-
manager: ConfigManagerLike,
|
|
267
|
-
workspaceRoot: string,
|
|
268
|
-
pathArg: string,
|
|
269
|
-
_options: SandboxProvisioningOptions,
|
|
270
|
-
): SandboxQemuInitBundle {
|
|
271
|
-
void manager;
|
|
272
|
-
void workspaceRoot;
|
|
273
|
-
void pathArg;
|
|
274
|
-
void _options;
|
|
275
|
-
sandboxExternalized('QEMU init bundle scaffolding');
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
export function scaffoldSandboxQemuSetupBundle(
|
|
279
|
-
manager: ConfigManagerLike,
|
|
280
|
-
workspaceRoot: string,
|
|
281
|
-
pathArg: string,
|
|
282
|
-
options: SandboxProvisioningOptions,
|
|
283
|
-
): SandboxQemuSetupBundle {
|
|
284
|
-
void manager;
|
|
285
|
-
void workspaceRoot;
|
|
286
|
-
void pathArg;
|
|
287
|
-
void options;
|
|
288
|
-
sandboxExternalized('QEMU setup bundle scaffolding');
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
export function bootstrapSandboxQemuSetupBundle(
|
|
292
|
-
manager: WritableConfigManagerLike,
|
|
293
|
-
workspaceRoot: string,
|
|
294
|
-
pathArg: string,
|
|
295
|
-
_sizeGb: number,
|
|
296
|
-
options: SandboxProvisioningOptions,
|
|
297
|
-
): SandboxQemuSetupBundle {
|
|
298
|
-
void manager;
|
|
299
|
-
void workspaceRoot;
|
|
300
|
-
void pathArg;
|
|
301
|
-
void _sizeGb;
|
|
302
|
-
void options;
|
|
303
|
-
sandboxExternalized('QEMU setup bootstrap');
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
export function inspectSandboxQemuSetupManifest(manifest: SandboxQemuSetupManifest): string {
|
|
307
|
-
return [
|
|
308
|
-
'QEMU sandbox setup manifest',
|
|
309
|
-
` wrapper: ${manifest.wrapperPath}`,
|
|
310
|
-
` image: ${manifest.imagePath}`,
|
|
311
|
-
` guest: ${manifest.recommendedSettings.guestUser}@${manifest.recommendedSettings.guestHost}:${manifest.recommendedSettings.guestPort}`,
|
|
312
|
-
` workspace: ${manifest.recommendedSettings.guestWorkspacePath}`,
|
|
313
|
-
].join('\n');
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
export function loadSandboxQemuSetupManifest(workspaceRoot: string, pathArg: string): SandboxQemuSetupManifest {
|
|
317
|
-
return JSON.parse(readFileSync(resolveWorkspacePath(workspaceRoot, pathArg), 'utf8')) as SandboxQemuSetupManifest;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
export function applySandboxQemuSetupManifest(manager: WritableConfigManagerLike, manifest: SandboxQemuSetupManifest): void {
|
|
321
|
-
void manager;
|
|
322
|
-
void manifest;
|
|
323
|
-
sandboxExternalized('QEMU manifest application');
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
export function exportSandboxGuestBundle(
|
|
327
|
-
manager: ConfigManagerLike,
|
|
328
|
-
workspaceRoot: string,
|
|
329
|
-
pathArg: string,
|
|
330
|
-
_options: SandboxProvisioningOptions,
|
|
331
|
-
): { readonly path: string; readonly bundle: SandboxGuestBundle } {
|
|
332
|
-
void manager;
|
|
333
|
-
void workspaceRoot;
|
|
334
|
-
void pathArg;
|
|
335
|
-
void _options;
|
|
336
|
-
sandboxExternalized('guest bundle export');
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
export function inspectSandboxGuestBundle(bundle: SandboxGuestBundle): string {
|
|
340
|
-
return [
|
|
341
|
-
'Sandbox guest bundle',
|
|
342
|
-
` wrapper: ${bundle.guest.wrapperPath}`,
|
|
343
|
-
` image: ${bundle.guest.imagePath || '(not configured)'}`,
|
|
344
|
-
` guest: ${bundle.guest.user}@${bundle.guest.host}:${bundle.guest.port}`,
|
|
345
|
-
` workspace: ${bundle.guest.workspacePath}`,
|
|
346
|
-
...bundle.nextSteps.map((step) => ` next: ${step}`),
|
|
347
|
-
].join('\n');
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
export function renderSandboxDoctor(manager: ConfigManagerLike): string {
|
|
351
|
-
const probe = probeSandboxBackends(manager);
|
|
352
|
-
return [
|
|
353
|
-
'Sandbox doctor',
|
|
354
|
-
` backend: ${probe.resolvedBackend}`,
|
|
355
|
-
...probe.backends.map((backend) => ` ${backend.id}: ${backend.available ? 'available' : 'missing'} (${backend.detail})`),
|
|
356
|
-
...probe.warnings.map((warning) => ` warning: ${warning}`),
|
|
357
|
-
].join('\n');
|
|
358
|
-
}
|