@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
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { createBrowserGoodVibesSdk } from '@pellux/goodvibes-sdk/browser';
|
|
4
|
+
import type { OperatorMethodOutput } from '@pellux/goodvibes-sdk/contracts';
|
|
5
|
+
import { formatEveryInterval } from '@pellux/goodvibes-sdk/platform/automation';
|
|
6
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
7
|
+
import type { ShellPathService } from '@/runtime/index.ts';
|
|
8
|
+
import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
|
|
9
|
+
import { SDK_VERSION } from '../version.ts';
|
|
10
|
+
import { isRoutineScheduleDeliverySurfaceKind } from './routine-schedule-args.ts';
|
|
11
|
+
import {
|
|
12
|
+
ROUTINE_SCHEDULE_LIST_METHOD,
|
|
13
|
+
ROUTINE_SCHEDULE_METHOD,
|
|
14
|
+
ROUTINE_SCHEDULE_ROUTE,
|
|
15
|
+
type AgentDaemonConnection,
|
|
16
|
+
type RoutineScheduleCorrelation,
|
|
17
|
+
type RoutineScheduleCorrelationFailure,
|
|
18
|
+
type RoutineScheduleCorrelationResult,
|
|
19
|
+
type RoutineScheduleCorrelationSuccess,
|
|
20
|
+
type RoutineScheduleDeliveryKind,
|
|
21
|
+
type RoutineScheduleKind,
|
|
22
|
+
type RoutineScheduleLiveRecord,
|
|
23
|
+
type RoutineSchedulePromotionPreview,
|
|
24
|
+
type RoutineSchedulePromotionResult,
|
|
25
|
+
type RoutineScheduleReceipt,
|
|
26
|
+
type RoutineScheduleReceiptDeliveryTarget,
|
|
27
|
+
type RoutineScheduleReceiptSnapshot,
|
|
28
|
+
} from './routine-schedule-promotion.ts';
|
|
29
|
+
|
|
30
|
+
type ScheduleCreateInput = RoutineSchedulePromotionPreview['payload'];
|
|
31
|
+
type ScheduleDeliveryInput = NonNullable<ScheduleCreateInput['delivery']>;
|
|
32
|
+
type ScheduleListOutput = OperatorMethodOutput<'schedules.list'>;
|
|
33
|
+
|
|
34
|
+
interface RoutineScheduleReceiptStoreFile {
|
|
35
|
+
readonly version: 1;
|
|
36
|
+
readonly receipts: readonly RoutineScheduleReceipt[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const RECEIPT_STORE_VERSION = 1;
|
|
40
|
+
|
|
41
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
42
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function readString(record: Record<string, unknown>, key: string): string | null {
|
|
46
|
+
const value = record[key];
|
|
47
|
+
return typeof value === 'string' ? value : null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function readBoolean(record: Record<string, unknown>, key: string): boolean | undefined {
|
|
51
|
+
const value = record[key];
|
|
52
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function readNumber(record: Record<string, unknown>, key: string): number | undefined {
|
|
56
|
+
const value = record[key];
|
|
57
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function nowIso(): string {
|
|
61
|
+
return new Date().toISOString();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function redactedDeliveryAddress(address: string | undefined): string | undefined {
|
|
65
|
+
if (!address) return undefined;
|
|
66
|
+
try {
|
|
67
|
+
const url = new URL(address);
|
|
68
|
+
return `${url.protocol}//${url.host}/...`;
|
|
69
|
+
} catch {
|
|
70
|
+
return '[redacted]';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function redactedDeliveryTargets(delivery: ScheduleDeliveryInput | undefined): readonly RoutineScheduleReceiptDeliveryTarget[] | undefined {
|
|
75
|
+
if (!delivery) return undefined;
|
|
76
|
+
return delivery.targets.map((target) => ({
|
|
77
|
+
kind: target.kind as RoutineScheduleDeliveryKind,
|
|
78
|
+
surfaceKind: typeof target.surfaceKind === 'string' && isRoutineScheduleDeliverySurfaceKind(target.surfaceKind) ? target.surfaceKind : undefined,
|
|
79
|
+
address: redactedDeliveryAddress(typeof target.address === 'string' ? target.address : undefined),
|
|
80
|
+
routeId: typeof target.routeId === 'string' ? target.routeId : undefined,
|
|
81
|
+
label: typeof target.label === 'string' ? target.label : undefined,
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function readReceipt(value: unknown): RoutineScheduleReceipt | null {
|
|
86
|
+
if (!isRecord(value)) return null;
|
|
87
|
+
const id = readString(value, 'id')?.trim();
|
|
88
|
+
const createdAt = readString(value, 'createdAt')?.trim();
|
|
89
|
+
const routineId = readString(value, 'routineId')?.trim();
|
|
90
|
+
const routineName = readString(value, 'routineName')?.trim();
|
|
91
|
+
const status = value.status === 'created' || value.status === 'failed' ? value.status : null;
|
|
92
|
+
const scheduleKind = value.scheduleKind === 'cron' || value.scheduleKind === 'every' || value.scheduleKind === 'at'
|
|
93
|
+
? value.scheduleKind
|
|
94
|
+
: null;
|
|
95
|
+
const scheduleValue = readString(value, 'scheduleValue')?.trim();
|
|
96
|
+
const target = isRecord(value.target) ? value.target : {};
|
|
97
|
+
const deliveryTargets = Array.isArray(value.deliveryTargets)
|
|
98
|
+
? value.deliveryTargets.map((target): RoutineScheduleReceiptDeliveryTarget | null => {
|
|
99
|
+
if (!isRecord(target)) return null;
|
|
100
|
+
const kind = target.kind === 'webhook' || target.kind === 'surface' || target.kind === 'integration' || target.kind === 'link'
|
|
101
|
+
? target.kind
|
|
102
|
+
: null;
|
|
103
|
+
if (!kind) return null;
|
|
104
|
+
const surfaceKind = readString(target, 'surfaceKind') ?? undefined;
|
|
105
|
+
return {
|
|
106
|
+
kind,
|
|
107
|
+
surfaceKind: surfaceKind && isRoutineScheduleDeliverySurfaceKind(surfaceKind) ? surfaceKind : undefined,
|
|
108
|
+
address: readString(target, 'address') ?? undefined,
|
|
109
|
+
routeId: readString(target, 'routeId') ?? undefined,
|
|
110
|
+
label: readString(target, 'label') ?? undefined,
|
|
111
|
+
};
|
|
112
|
+
}).filter((target): target is RoutineScheduleReceiptDeliveryTarget => target !== null)
|
|
113
|
+
: undefined;
|
|
114
|
+
if (!id || !createdAt || !routineId || !routineName || !status || !scheduleKind || !scheduleValue) return null;
|
|
115
|
+
return {
|
|
116
|
+
id,
|
|
117
|
+
createdAt,
|
|
118
|
+
routineId,
|
|
119
|
+
routineName,
|
|
120
|
+
route: ROUTINE_SCHEDULE_ROUTE,
|
|
121
|
+
method: ROUTINE_SCHEDULE_METHOD,
|
|
122
|
+
status,
|
|
123
|
+
daemonBaseUrl: readString(value, 'daemonBaseUrl') ?? '',
|
|
124
|
+
scheduleId: readString(value, 'scheduleId') ?? undefined,
|
|
125
|
+
scheduleStatus: readString(value, 'scheduleStatus') ?? undefined,
|
|
126
|
+
scheduleName: readString(value, 'scheduleName') ?? routineName,
|
|
127
|
+
scheduleKind,
|
|
128
|
+
scheduleValue,
|
|
129
|
+
timezone: readString(value, 'timezone') ?? undefined,
|
|
130
|
+
provider: readString(value, 'provider') ?? undefined,
|
|
131
|
+
model: readString(value, 'model') ?? undefined,
|
|
132
|
+
enabled: value.enabled !== false,
|
|
133
|
+
target: {
|
|
134
|
+
kind: readString(target, 'kind') ?? undefined,
|
|
135
|
+
surfaceKind: readString(target, 'surfaceKind') ?? undefined,
|
|
136
|
+
preserveThread: readBoolean(target, 'preserveThread'),
|
|
137
|
+
createIfMissing: readBoolean(target, 'createIfMissing'),
|
|
138
|
+
},
|
|
139
|
+
deliveryMode: readString(value, 'deliveryMode') ?? undefined,
|
|
140
|
+
deliveryTargets,
|
|
141
|
+
failureKind: value.failureKind === 'confirmation_required'
|
|
142
|
+
|| value.failureKind === 'auth_required'
|
|
143
|
+
|| value.failureKind === 'daemon_unavailable'
|
|
144
|
+
|| value.failureKind === 'version_mismatch'
|
|
145
|
+
|| value.failureKind === 'daemon_route_unavailable'
|
|
146
|
+
|| value.failureKind === 'daemon_error'
|
|
147
|
+
? value.failureKind
|
|
148
|
+
: undefined,
|
|
149
|
+
failureError: readString(value, 'failureError') ?? undefined,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function parseReceiptStore(raw: string): RoutineScheduleReceiptStoreFile {
|
|
154
|
+
const parsed: unknown = JSON.parse(raw);
|
|
155
|
+
if (!isRecord(parsed)) return { version: RECEIPT_STORE_VERSION, receipts: [] };
|
|
156
|
+
return {
|
|
157
|
+
version: RECEIPT_STORE_VERSION,
|
|
158
|
+
receipts: Array.isArray(parsed.receipts)
|
|
159
|
+
? parsed.receipts.map(readReceipt).filter((receipt): receipt is RoutineScheduleReceipt => receipt !== null)
|
|
160
|
+
: [],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function formatReceiptStore(store: RoutineScheduleReceiptStoreFile): string {
|
|
165
|
+
return `${JSON.stringify(store, null, 2)}\n`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function receiptId(createdAt: string, routineId: string, existing: readonly RoutineScheduleReceipt[]): string {
|
|
169
|
+
const dayStamp = createdAt.slice(0, 10).replace(/-/g, '');
|
|
170
|
+
const base = `routine-schedule-${routineId}-${dayStamp}`.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
|
|
171
|
+
const ids = new Set(existing.map((receipt) => receipt.id));
|
|
172
|
+
if (!ids.has(base)) return base;
|
|
173
|
+
for (let index = 2; index < 1000; index += 1) {
|
|
174
|
+
const candidate = `${base}-${index}`;
|
|
175
|
+
if (!ids.has(candidate)) return candidate;
|
|
176
|
+
}
|
|
177
|
+
return `${base}-${existing.length + 1}`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function scheduleValue(payload: ScheduleCreateInput): string {
|
|
181
|
+
if (payload.kind === 'cron') return String(payload.cron ?? '');
|
|
182
|
+
if (payload.kind === 'every') return String(payload.every ?? '');
|
|
183
|
+
return String(payload.at ?? '');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function scheduleKind(payload: ScheduleCreateInput): RoutineScheduleKind {
|
|
187
|
+
if (payload.kind === 'cron' || payload.kind === 'every' || payload.kind === 'at') return payload.kind;
|
|
188
|
+
throw new Error('Routine schedule payload is missing a schedule kind.');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function targetSummary(payload: ScheduleCreateInput): RoutineScheduleReceipt['target'] {
|
|
192
|
+
return isRecord(payload.target)
|
|
193
|
+
? {
|
|
194
|
+
kind: typeof payload.target.kind === 'string' ? payload.target.kind : undefined,
|
|
195
|
+
surfaceKind: typeof payload.target.surfaceKind === 'string' ? payload.target.surfaceKind : undefined,
|
|
196
|
+
preserveThread: typeof payload.target.preserveThread === 'boolean' ? payload.target.preserveThread : undefined,
|
|
197
|
+
createIfMissing: typeof payload.target.createIfMissing === 'boolean' ? payload.target.createIfMissing : undefined,
|
|
198
|
+
}
|
|
199
|
+
: {};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function deliveryMode(payload: ScheduleCreateInput): string | undefined {
|
|
203
|
+
return isRecord(payload.delivery) && typeof payload.delivery.mode === 'string' ? payload.delivery.mode : undefined;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function resultScheduleRecord(result: RoutineSchedulePromotionResult): Record<string, unknown> {
|
|
207
|
+
return result.ok && isRecord(result.schedule) ? result.schedule : {};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function buildReceipt(
|
|
211
|
+
existing: readonly RoutineScheduleReceipt[],
|
|
212
|
+
connection: AgentDaemonConnection,
|
|
213
|
+
preview: RoutineSchedulePromotionPreview,
|
|
214
|
+
result: RoutineSchedulePromotionResult,
|
|
215
|
+
): RoutineScheduleReceipt {
|
|
216
|
+
const createdAt = nowIso();
|
|
217
|
+
const kind = scheduleKind(preview.payload);
|
|
218
|
+
const schedule = resultScheduleRecord(result);
|
|
219
|
+
const scheduleId = readString(schedule, 'id') ?? undefined;
|
|
220
|
+
const scheduleStatus = readString(schedule, 'status') ?? (schedule.enabled === false ? 'paused' : schedule.enabled === true ? 'enabled' : undefined);
|
|
221
|
+
return {
|
|
222
|
+
id: receiptId(createdAt, preview.routineId, existing),
|
|
223
|
+
createdAt,
|
|
224
|
+
routineId: preview.routineId,
|
|
225
|
+
routineName: preview.routineName,
|
|
226
|
+
route: ROUTINE_SCHEDULE_ROUTE,
|
|
227
|
+
method: ROUTINE_SCHEDULE_METHOD,
|
|
228
|
+
status: result.ok ? 'created' : 'failed',
|
|
229
|
+
daemonBaseUrl: connection.baseUrl,
|
|
230
|
+
scheduleId,
|
|
231
|
+
scheduleStatus,
|
|
232
|
+
scheduleName: String(preview.payload.name ?? `Agent routine: ${preview.routineName}`),
|
|
233
|
+
scheduleKind: kind,
|
|
234
|
+
scheduleValue: scheduleValue(preview.payload),
|
|
235
|
+
timezone: kind === 'cron' ? preview.payload.timezone : undefined,
|
|
236
|
+
provider: preview.payload.provider,
|
|
237
|
+
model: preview.payload.model,
|
|
238
|
+
enabled: preview.payload.enabled !== false,
|
|
239
|
+
target: targetSummary(preview.payload),
|
|
240
|
+
deliveryMode: deliveryMode(preview.payload),
|
|
241
|
+
deliveryTargets: redactedDeliveryTargets(preview.payload.delivery),
|
|
242
|
+
failureKind: result.ok ? undefined : result.kind,
|
|
243
|
+
failureError: result.ok ? undefined : result.error,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function routineScheduleReceiptStorePath(shellPaths: ShellPathService): string {
|
|
248
|
+
return shellPaths.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, 'routines', 'schedule-receipts.json');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export class RoutineScheduleReceiptStore {
|
|
252
|
+
public constructor(private readonly storePath: string) {}
|
|
253
|
+
|
|
254
|
+
public static fromShellPaths(shellPaths: ShellPathService): RoutineScheduleReceiptStore {
|
|
255
|
+
return new RoutineScheduleReceiptStore(routineScheduleReceiptStorePath(shellPaths));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
public snapshot(): RoutineScheduleReceiptSnapshot {
|
|
259
|
+
const store = this.readStore();
|
|
260
|
+
return {
|
|
261
|
+
path: this.storePath,
|
|
262
|
+
receipts: [...store.receipts].sort((left, right) => right.createdAt.localeCompare(left.createdAt)),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
public get(id: string): RoutineScheduleReceipt | null {
|
|
267
|
+
const normalized = id.trim().toLowerCase();
|
|
268
|
+
if (!normalized) return null;
|
|
269
|
+
return this.snapshot().receipts.find((receipt) => receipt.id.toLowerCase() === normalized) ?? null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
public append(connection: AgentDaemonConnection, preview: RoutineSchedulePromotionPreview, result: RoutineSchedulePromotionResult): RoutineScheduleReceipt {
|
|
273
|
+
const store = this.readStore();
|
|
274
|
+
const receipt = buildReceipt(store.receipts, connection, preview, result);
|
|
275
|
+
this.writeStore({ ...store, receipts: [...store.receipts, receipt] });
|
|
276
|
+
return receipt;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private readStore(): RoutineScheduleReceiptStoreFile {
|
|
280
|
+
if (!existsSync(this.storePath)) return { version: RECEIPT_STORE_VERSION, receipts: [] };
|
|
281
|
+
try {
|
|
282
|
+
return parseReceiptStore(readFileSync(this.storePath, 'utf-8'));
|
|
283
|
+
} catch (error) {
|
|
284
|
+
throw new Error(`Could not read Agent routine schedule receipt store: ${summarizeError(error)}`);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private writeStore(store: RoutineScheduleReceiptStoreFile): void {
|
|
289
|
+
mkdirSync(dirname(this.storePath), { recursive: true });
|
|
290
|
+
const tmpPath = `${this.storePath}.tmp`;
|
|
291
|
+
writeFileSync(tmpPath, formatReceiptStore(store), 'utf-8');
|
|
292
|
+
renameSync(tmpPath, this.storePath);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function normalizeForMatch(value: string | undefined): string {
|
|
297
|
+
return (value ?? '').trim().toLowerCase();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function isoTime(value: string): string | null {
|
|
301
|
+
const time = new Date(value).getTime();
|
|
302
|
+
return Number.isFinite(time) ? new Date(time).toISOString() : null;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function readLiveScheduleDefinition(value: unknown): {
|
|
306
|
+
readonly kind?: RoutineScheduleKind;
|
|
307
|
+
readonly value?: string;
|
|
308
|
+
readonly timezone?: string;
|
|
309
|
+
} {
|
|
310
|
+
if (!isRecord(value)) return {};
|
|
311
|
+
if (value.kind === 'cron') {
|
|
312
|
+
return {
|
|
313
|
+
kind: 'cron',
|
|
314
|
+
value: readString(value, 'expression') ?? undefined,
|
|
315
|
+
timezone: readString(value, 'timezone') ?? undefined,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
if (value.kind === 'every') {
|
|
319
|
+
const intervalMs = readNumber(value, 'intervalMs');
|
|
320
|
+
return {
|
|
321
|
+
kind: 'every',
|
|
322
|
+
value: intervalMs === undefined ? undefined : formatEveryInterval(intervalMs),
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
if (value.kind === 'at') {
|
|
326
|
+
const at = readNumber(value, 'at');
|
|
327
|
+
return {
|
|
328
|
+
kind: 'at',
|
|
329
|
+
value: at === undefined ? undefined : new Date(at).toISOString(),
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
return {};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function readLiveScheduleRecord(value: unknown): RoutineScheduleLiveRecord | null {
|
|
336
|
+
if (!isRecord(value)) return null;
|
|
337
|
+
const id = readString(value, 'id')?.trim();
|
|
338
|
+
const name = readString(value, 'name')?.trim();
|
|
339
|
+
if (!id || !name) return null;
|
|
340
|
+
const schedule = readLiveScheduleDefinition(value.schedule);
|
|
341
|
+
return {
|
|
342
|
+
id,
|
|
343
|
+
name,
|
|
344
|
+
status: readString(value, 'status') ?? undefined,
|
|
345
|
+
enabled: readBoolean(value, 'enabled'),
|
|
346
|
+
scheduleKind: schedule.kind,
|
|
347
|
+
scheduleValue: schedule.value,
|
|
348
|
+
timezone: schedule.timezone,
|
|
349
|
+
nextRunAt: readNumber(value, 'nextRunAt'),
|
|
350
|
+
lastRunAt: readNumber(value, 'lastRunAt'),
|
|
351
|
+
runCount: readNumber(value, 'runCount'),
|
|
352
|
+
successCount: readNumber(value, 'successCount'),
|
|
353
|
+
failureCount: readNumber(value, 'failureCount'),
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function readLiveSchedules(output: ScheduleListOutput): readonly RoutineScheduleLiveRecord[] {
|
|
358
|
+
const record: Record<string, unknown> = isRecord(output) ? output : {};
|
|
359
|
+
const jobs: readonly unknown[] = Array.isArray(record.jobs) ? record.jobs : [];
|
|
360
|
+
return jobs.map(readLiveScheduleRecord).filter((schedule): schedule is RoutineScheduleLiveRecord => schedule !== null);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function cadenceMatches(receipt: RoutineScheduleReceipt, schedule: RoutineScheduleLiveRecord): boolean {
|
|
364
|
+
if (receipt.scheduleKind !== schedule.scheduleKind) return false;
|
|
365
|
+
if (receipt.scheduleKind === 'at') {
|
|
366
|
+
const left = isoTime(receipt.scheduleValue);
|
|
367
|
+
const right = schedule.scheduleValue ? isoTime(schedule.scheduleValue) : null;
|
|
368
|
+
return Boolean(left && right && left === right);
|
|
369
|
+
}
|
|
370
|
+
return normalizeForMatch(receipt.scheduleValue) === normalizeForMatch(schedule.scheduleValue);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function findScheduleForReceipt(
|
|
374
|
+
receipt: RoutineScheduleReceipt,
|
|
375
|
+
schedules: readonly RoutineScheduleLiveRecord[],
|
|
376
|
+
): { readonly reason: RoutineScheduleCorrelation['matchReason']; readonly schedule?: RoutineScheduleLiveRecord } {
|
|
377
|
+
if (receipt.scheduleId) {
|
|
378
|
+
const byId = schedules.find((schedule) => schedule.id === receipt.scheduleId);
|
|
379
|
+
if (byId) return { reason: 'schedule-id', schedule: byId };
|
|
380
|
+
}
|
|
381
|
+
const byNameAndCadence = schedules.find((schedule) => (
|
|
382
|
+
normalizeForMatch(schedule.name) === normalizeForMatch(receipt.scheduleName)
|
|
383
|
+
&& cadenceMatches(receipt, schedule)
|
|
384
|
+
));
|
|
385
|
+
return byNameAndCadence
|
|
386
|
+
? { reason: 'name-and-cadence', schedule: byNameAndCadence }
|
|
387
|
+
: { reason: 'not-found' };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function correlateReceipts(
|
|
391
|
+
receipts: readonly RoutineScheduleReceipt[],
|
|
392
|
+
schedules: readonly RoutineScheduleLiveRecord[],
|
|
393
|
+
): readonly RoutineScheduleCorrelation[] {
|
|
394
|
+
return receipts.map((receipt) => {
|
|
395
|
+
if (receipt.status === 'failed') {
|
|
396
|
+
return {
|
|
397
|
+
receipt,
|
|
398
|
+
liveStatus: 'failed-receipt',
|
|
399
|
+
matchReason: 'failed-receipt',
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
const match = findScheduleForReceipt(receipt, schedules);
|
|
403
|
+
return match.schedule
|
|
404
|
+
? {
|
|
405
|
+
receipt,
|
|
406
|
+
liveStatus: 'matched',
|
|
407
|
+
matchReason: match.reason,
|
|
408
|
+
schedule: match.schedule,
|
|
409
|
+
}
|
|
410
|
+
: {
|
|
411
|
+
receipt,
|
|
412
|
+
liveStatus: 'missing',
|
|
413
|
+
matchReason: 'not-found',
|
|
414
|
+
};
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
async function fetchDaemonStatus(connection: AgentDaemonConnection): Promise<{
|
|
419
|
+
readonly ok: boolean;
|
|
420
|
+
readonly status: number;
|
|
421
|
+
readonly body: unknown;
|
|
422
|
+
}> {
|
|
423
|
+
try {
|
|
424
|
+
const response = await fetch(`${connection.baseUrl}/status`, {
|
|
425
|
+
headers: connection.token ? { authorization: `Bearer ${connection.token}` } : undefined,
|
|
426
|
+
});
|
|
427
|
+
const text = await response.text();
|
|
428
|
+
let body: unknown = text;
|
|
429
|
+
try {
|
|
430
|
+
body = text.trim() ? JSON.parse(text) as unknown : {};
|
|
431
|
+
} catch {
|
|
432
|
+
body = text;
|
|
433
|
+
}
|
|
434
|
+
return { ok: response.ok, status: response.status, body };
|
|
435
|
+
} catch (error) {
|
|
436
|
+
return { ok: false, status: 0, body: summarizeError(error) };
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
async function classifyScheduleListError(
|
|
441
|
+
error: unknown,
|
|
442
|
+
connection: AgentDaemonConnection,
|
|
443
|
+
): Promise<RoutineScheduleCorrelationFailure> {
|
|
444
|
+
const message = summarizeError(error);
|
|
445
|
+
const lower = message.toLowerCase();
|
|
446
|
+
if (lower.includes('401') || lower.includes('unauthorized') || lower.includes('auth')) {
|
|
447
|
+
return { ok: false, kind: 'auth_required', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
|
|
448
|
+
}
|
|
449
|
+
if (lower.includes('404') || lower.includes('not found')) {
|
|
450
|
+
const daemon = await fetchDaemonStatus(connection);
|
|
451
|
+
const record = isRecord(daemon.body) ? daemon.body : {};
|
|
452
|
+
const daemonVersion = readString(record, 'version') ?? 'unknown';
|
|
453
|
+
if (daemon.ok && daemonVersion !== SDK_VERSION) {
|
|
454
|
+
return {
|
|
455
|
+
ok: false,
|
|
456
|
+
kind: 'version_mismatch',
|
|
457
|
+
error: `External daemon SDK version ${daemonVersion} does not match Agent SDK pin ${SDK_VERSION}; schedules.list is unavailable.`,
|
|
458
|
+
route: ROUTINE_SCHEDULE_ROUTE,
|
|
459
|
+
baseUrl: connection.baseUrl,
|
|
460
|
+
daemonVersion,
|
|
461
|
+
expectedSdkVersion: SDK_VERSION,
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
return { ok: false, kind: 'daemon_route_unavailable', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
|
|
465
|
+
}
|
|
466
|
+
if (lower.includes('fetch') || lower.includes('connect') || lower.includes('econnrefused')) {
|
|
467
|
+
return { ok: false, kind: 'daemon_unavailable', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
|
|
468
|
+
}
|
|
469
|
+
return { ok: false, kind: 'daemon_error', error: message, route: ROUTINE_SCHEDULE_ROUTE, baseUrl: connection.baseUrl };
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export async function reconcileRoutineScheduleReceipts(
|
|
473
|
+
connection: AgentDaemonConnection,
|
|
474
|
+
snapshot: RoutineScheduleReceiptSnapshot,
|
|
475
|
+
): Promise<RoutineScheduleCorrelationResult> {
|
|
476
|
+
if (!connection.token) {
|
|
477
|
+
return {
|
|
478
|
+
ok: false,
|
|
479
|
+
kind: 'auth_required',
|
|
480
|
+
error: `No daemon operator token found at ${connection.tokenPath}`,
|
|
481
|
+
route: ROUTINE_SCHEDULE_ROUTE,
|
|
482
|
+
baseUrl: connection.baseUrl,
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
try {
|
|
486
|
+
const sdk = createBrowserGoodVibesSdk({ baseUrl: connection.baseUrl, authToken: connection.token });
|
|
487
|
+
const output = await sdk.operator.invoke(ROUTINE_SCHEDULE_LIST_METHOD, {});
|
|
488
|
+
const schedules = readLiveSchedules(output);
|
|
489
|
+
const result: RoutineScheduleCorrelationSuccess = {
|
|
490
|
+
ok: true,
|
|
491
|
+
kind: ROUTINE_SCHEDULE_LIST_METHOD,
|
|
492
|
+
route: ROUTINE_SCHEDULE_ROUTE,
|
|
493
|
+
baseUrl: connection.baseUrl,
|
|
494
|
+
scheduleCount: schedules.length,
|
|
495
|
+
receiptCount: snapshot.receipts.length,
|
|
496
|
+
correlations: correlateReceipts(snapshot.receipts, schedules),
|
|
497
|
+
};
|
|
498
|
+
return result;
|
|
499
|
+
} catch (error) {
|
|
500
|
+
return classifyScheduleListError(error, connection);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
@@ -270,11 +270,11 @@ function findDisallowedKnowledgeScopeFlag(args: readonly string[]): string | nul
|
|
|
270
270
|
'--space',
|
|
271
271
|
'--knowledge-space',
|
|
272
272
|
'--knowledge-space-id',
|
|
273
|
-
'--
|
|
273
|
+
['--knowledge', 'SpaceId'].join(''),
|
|
274
274
|
'--include-all-spaces',
|
|
275
|
-
'--
|
|
276
|
-
'--
|
|
277
|
-
'--home-graph',
|
|
275
|
+
['--include', 'AllSpaces'].join(''),
|
|
276
|
+
['--home', 'graph'].join(''),
|
|
277
|
+
['--home', '-graph'].join(''),
|
|
278
278
|
];
|
|
279
279
|
for (const token of args) {
|
|
280
280
|
for (const flag of disallowed) {
|
|
@@ -287,7 +287,7 @@ function findDisallowedKnowledgeScopeFlag(args: readonly string[]): string | nul
|
|
|
287
287
|
function formatScopeFlagRejection(flag: string): string {
|
|
288
288
|
return [
|
|
289
289
|
`Agent Knowledge is isolated; ${flag} is not accepted.`,
|
|
290
|
-
'GoodVibes Agent must not use default Knowledge/Wiki
|
|
290
|
+
'GoodVibes Agent must not use default Knowledge/Wiki or non-Agent product spaces.',
|
|
291
291
|
'Use only /api/goodvibes-agent/knowledge/* Agent-owned routes.',
|
|
292
292
|
].join('\n');
|
|
293
293
|
}
|
|
@@ -407,7 +407,7 @@ function buildDelegationBody(task: string, wrfcRequested: boolean): string {
|
|
|
407
407
|
'Agent policy:',
|
|
408
408
|
'- GoodVibes Agent is not the coding TUI.',
|
|
409
409
|
'- Preserve the full original ask.',
|
|
410
|
-
'- GoodVibes TUI owns file edits, git/worktree flows,
|
|
410
|
+
'- GoodVibes TUI owns file edits, git/worktree flows, runtime-isolation UX, and any WRFC owner chain.',
|
|
411
411
|
wrfcRequested
|
|
412
412
|
? '- WRFC was explicitly requested by the Agent user for this build/fix/review delegation.'
|
|
413
413
|
: '- WRFC was not explicitly requested; do not turn this into WRFC solely because it came from Agent.',
|
package/src/cli/help.ts
CHANGED
|
@@ -200,7 +200,7 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
|
|
|
200
200
|
'knowledge search <query> [--limit <n>]',
|
|
201
201
|
'knowledge ingest-url <url> [--title <title>] [--tags a,b] --yes',
|
|
202
202
|
],
|
|
203
|
-
summary: 'Call isolated Agent Knowledge/Wiki routes under /api/goodvibes-agent/knowledge. No default wiki or
|
|
203
|
+
summary: 'Call isolated Agent Knowledge/Wiki routes under /api/goodvibes-agent/knowledge. No default wiki or non-Agent fallback.',
|
|
204
204
|
examples: [
|
|
205
205
|
'knowledge status',
|
|
206
206
|
'knowledge ask "What is GoodVibes Agent?"',
|
|
@@ -210,12 +210,12 @@ const COMMAND_HELP: Record<string, CommandHelp> = {
|
|
|
210
210
|
},
|
|
211
211
|
ask: {
|
|
212
212
|
usage: ['ask <question> [--limit <n>] [--mode concise|standard|detailed]'],
|
|
213
|
-
summary: 'Shortcut for isolated Agent Knowledge ask. This never queries default Knowledge/Wiki or
|
|
213
|
+
summary: 'Shortcut for isolated Agent Knowledge ask. This never queries default Knowledge/Wiki or non-Agent knowledge.',
|
|
214
214
|
examples: ['ask "What is GoodVibes Agent?"', 'ask "release checklist" --mode concise'],
|
|
215
215
|
},
|
|
216
216
|
search: {
|
|
217
217
|
usage: ['search <query> [--limit <n>]'],
|
|
218
|
-
summary: 'Shortcut for isolated Agent Knowledge search. This never queries default Knowledge/Wiki or
|
|
218
|
+
summary: 'Shortcut for isolated Agent Knowledge search. This never queries default Knowledge/Wiki or non-Agent knowledge.',
|
|
219
219
|
examples: ['search "release checklist"', 'search "operator workspace" --limit 5'],
|
|
220
220
|
},
|
|
221
221
|
delegate: {
|
|
@@ -355,25 +355,3 @@ export function renderGoodVibesCommandHelp(topic: string, binary = 'goodvibes-ag
|
|
|
355
355
|
] : []),
|
|
356
356
|
].join('\n');
|
|
357
357
|
}
|
|
358
|
-
|
|
359
|
-
export function renderGoodVibesDaemonHelp(binary = 'goodvibes-daemon'): string {
|
|
360
|
-
return [
|
|
361
|
-
`Usage: ${binary} [OPTIONS]`,
|
|
362
|
-
'',
|
|
363
|
-
'Unavailable in GoodVibes Agent.',
|
|
364
|
-
'',
|
|
365
|
-
'GoodVibes Agent connects to an already-running GoodVibes daemon. It does not start, install, restart, or own daemon/listener lifecycle.',
|
|
366
|
-
'Use GoodVibes TUI or your daemon host tooling to manage the daemon, then connect with goodvibes-agent.',
|
|
367
|
-
'',
|
|
368
|
-
'Options:',
|
|
369
|
-
' --daemon-home <dir> Override daemon home',
|
|
370
|
-
' --working-dir <dir> Override working directory',
|
|
371
|
-
' -C, --cd <dir> Alias for --working-dir',
|
|
372
|
-
' --provider <id> Override provider',
|
|
373
|
-
' -m, --model <registryKey> Override model. provider:model infers --provider',
|
|
374
|
-
' --hostname <host> Hostname hint for printed connection info',
|
|
375
|
-
' --port <port> Control-plane port override when supported',
|
|
376
|
-
' -h, --help Print help',
|
|
377
|
-
' -v, --version Print version',
|
|
378
|
-
].join('\n');
|
|
379
|
-
}
|
|
@@ -46,13 +46,13 @@ const REQUIRED_TARBALL_PATHS = [
|
|
|
46
46
|
] as const;
|
|
47
47
|
const FORBIDDEN_TARBALL_PREFIXES = ['.github/', 'src/test/', 'src/.test/', '.goodvibes/memory/', '.goodvibes/agents/', 'vendor/'] as const;
|
|
48
48
|
const FORBIDDEN_TARBALL_DOCS = [
|
|
49
|
-
'docs/
|
|
50
|
-
'docs/
|
|
51
|
-
'docs/homeassistant-surface.md',
|
|
49
|
+
['docs/cloud', 'flare-batch.md'].join(''),
|
|
50
|
+
['docs/home', 'assistant-surface.md'].join(''),
|
|
52
51
|
'docs/wrfc/',
|
|
53
52
|
] as const;
|
|
54
53
|
const PACKAGE_FACING_TEXT_PATHS = [
|
|
55
54
|
'README.md',
|
|
55
|
+
'CHANGELOG.md',
|
|
56
56
|
'docs/README.md',
|
|
57
57
|
'docs/getting-started.md',
|
|
58
58
|
'docs/deployment-and-services.md',
|
|
@@ -61,15 +61,22 @@ const PACKAGE_FACING_TEXT_PATHS = [
|
|
|
61
61
|
'.goodvibes/skills/add-provider/SKILL.md',
|
|
62
62
|
] as const;
|
|
63
63
|
const PACKAGE_FACING_FORBIDDEN_TEXT = [
|
|
64
|
-
'/api/knowledge',
|
|
65
|
-
'/api/
|
|
66
|
-
'
|
|
67
|
-
'
|
|
68
|
-
'
|
|
69
|
-
'@pellux/goodvibes-tui',
|
|
70
|
-
'@pellux/goodvibes-daemon',
|
|
71
|
-
'goodvibes-daemon',
|
|
72
|
-
'~/.goodvibes/tui',
|
|
64
|
+
['/api/', 'knowledge'].join(''),
|
|
65
|
+
['/api/home', 'assistant'].join(''),
|
|
66
|
+
['home', 'assistant.home', 'Graph'].join(''),
|
|
67
|
+
['include', 'AllSpaces'].join(''),
|
|
68
|
+
['knowledge', 'SpaceId'].join(''),
|
|
69
|
+
['@pellux/goodvibes-', 'tui'].join(''),
|
|
70
|
+
['@pellux/goodvibes-', 'daemon'].join(''),
|
|
71
|
+
['goodvibes-', 'daemon'].join(''),
|
|
72
|
+
['~/.goodvibes/', 'tui'].join(''),
|
|
73
|
+
['Home', ' Assistant'].join(''),
|
|
74
|
+
['Home', 'Graph'].join(''),
|
|
75
|
+
['Cloud', 'flare'].join(''),
|
|
76
|
+
['Open', 'Claw'].join(''),
|
|
77
|
+
['Her', 'mes'].join(''),
|
|
78
|
+
['capabilities', ' audit'].join(''),
|
|
79
|
+
['capabilities', ' command'].join(''),
|
|
73
80
|
'Every plan must have a multi-agent execution strategy',
|
|
74
81
|
'NEVER skip WRFC',
|
|
75
82
|
'ALWAYS work in parallel when implementing a plan',
|
|
@@ -109,7 +116,7 @@ function verifyBin(root: string, command: typeof REQUIRED_BIN_COMMANDS[number],
|
|
|
109
116
|
};
|
|
110
117
|
}
|
|
111
118
|
|
|
112
|
-
function
|
|
119
|
+
function registryPackDryRun(root: string): { readonly files: readonly string[]; readonly entryCount: number; readonly unpackedSize: number } {
|
|
113
120
|
const raw = execSync('npm pack --json --dry-run', {
|
|
114
121
|
cwd: root,
|
|
115
122
|
encoding: 'utf-8',
|
|
@@ -156,7 +163,7 @@ export function verifyPackageCliInstall(root: string): PackageCliVerificationRep
|
|
|
156
163
|
const pkg = readPackageJson(root);
|
|
157
164
|
const bin = pkg.bin && typeof pkg.bin === 'object' ? pkg.bin as Record<string, string | undefined> : {};
|
|
158
165
|
const bins = REQUIRED_BIN_COMMANDS.map((command) => verifyBin(root, command, bin[command]));
|
|
159
|
-
const pack =
|
|
166
|
+
const pack = registryPackDryRun(root);
|
|
160
167
|
const requiredPathsPresent = REQUIRED_TARBALL_PATHS.filter((path) => pack.files.includes(path));
|
|
161
168
|
const forbiddenPaths = pack.files.filter((path) => {
|
|
162
169
|
if (FORBIDDEN_TARBALL_PREFIXES.some((prefix) => path.startsWith(prefix))) return true;
|
|
@@ -173,10 +180,10 @@ export function verifyPackageCliInstall(root: string): PackageCliVerificationRep
|
|
|
173
180
|
if (!item.hasSourceEntrypoint) issues.push(`bin target does not import the Agent source entrypoint: ${item.command}`);
|
|
174
181
|
}
|
|
175
182
|
for (const path of REQUIRED_TARBALL_PATHS) {
|
|
176
|
-
if (!pack.files.includes(path)) issues.push(`
|
|
183
|
+
if (!pack.files.includes(path)) issues.push(`registry tarball missing required path: ${path}`);
|
|
177
184
|
}
|
|
178
185
|
for (const path of forbiddenPaths) {
|
|
179
|
-
issues.push(`
|
|
186
|
+
issues.push(`registry tarball includes forbidden path: ${path}`);
|
|
180
187
|
}
|
|
181
188
|
for (const failure of packageFacingText.failures) {
|
|
182
189
|
issues.push(failure);
|
package/src/cli/redaction.ts
CHANGED
|
@@ -64,7 +64,10 @@ export function redactConfig<T>(config: T): RedactedConfigResult<T> {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
export function redactText(input: string): string {
|
|
67
|
-
let output = input
|
|
67
|
+
let output = input.replace(
|
|
68
|
+
/\b([A-Za-z0-9_]*(?:token|secret|password|api[_-]?key|key)[A-Za-z0-9_]*\s*=\s*)([^ \t\r\n"'`]+)/gi,
|
|
69
|
+
`$1${REDACTED_VALUE}`,
|
|
70
|
+
);
|
|
68
71
|
for (const pattern of SECRET_LIKE_TEXT_PATTERNS) {
|
|
69
72
|
output = output.replace(pattern, REDACTED_VALUE);
|
|
70
73
|
}
|