@pellux/goodvibes-agent 1.5.6 → 1.5.7
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 +7 -0
- package/README.md +1 -1
- package/dist/package/main.js +1512 -1239
- package/package.json +1 -1
- package/src/agent/prompt-context-receipts.ts +5 -3
- package/src/agent/vibe-file.ts +84 -3
- package/src/config/credential-status.ts +165 -0
- package/src/config/index.ts +12 -0
- package/src/config/secrets.ts +17 -0
- package/src/runtime/bootstrap-core.ts +32 -1
- package/src/tools/agent-harness-prompt-context.ts +5 -2
- package/src/version.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pellux/goodvibes-agent",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.7",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "GoodVibes personal operator assistant TUI with a proactive Agent product brain, isolated Agent Knowledge, local profiles, routines, skills, personas, and explicit build delegation.",
|
|
6
6
|
"type": "module",
|
|
@@ -9,7 +9,7 @@ import { AgentPersonaRegistry, buildActivePersonaPrompt } from './persona-regist
|
|
|
9
9
|
import { buildProjectContextPrompt, discoverProjectContextFiles } from './project-context-files.ts';
|
|
10
10
|
import { AgentRoutineRegistry, buildEnabledRoutinesPrompt, evaluateAgentRoutineReadiness } from './routine-registry.ts';
|
|
11
11
|
import { AgentSkillRegistry, buildEnabledSkillsPrompt, evaluateAgentSkillBundleReadiness, evaluateAgentSkillReadiness } from './skill-registry.ts';
|
|
12
|
-
import {
|
|
12
|
+
import { buildVibeProjectionPrompt, discoverVibeFiles } from './vibe-file.ts';
|
|
13
13
|
|
|
14
14
|
export type PromptContextReceiptStatus = 'active' | 'attention' | 'empty' | 'unavailable';
|
|
15
15
|
export type PromptContextReceiptSource = 'turn' | 'follow_up' | 'manual';
|
|
@@ -140,7 +140,9 @@ function receiptSegment(input: Omit<PromptContextReceiptSegment, 'approxTokens'>
|
|
|
140
140
|
|
|
141
141
|
function buildRuntimePromptReceiptSegments(input: RuntimePromptCompositionInput): readonly PromptContextReceiptSegment[] {
|
|
142
142
|
const vibe = discoverVibeFiles(input.shellPaths);
|
|
143
|
-
|
|
143
|
+
// W6-C2 (E6): the VIBE prompt is a PROJECTION of persona/constraint records, not a
|
|
144
|
+
// re-read of the files (discoverVibeFiles above stays for the file-discovery receipt).
|
|
145
|
+
const vibePrompt = buildVibeProjectionPrompt(input.memoryRegistry) ?? '';
|
|
144
146
|
const projectContext = discoverProjectContextFiles(input.shellPaths);
|
|
145
147
|
const projectContextPrompt = buildProjectContextPrompt(input.shellPaths) ?? '';
|
|
146
148
|
const memoryRecords = input.memoryRegistry.getAll();
|
|
@@ -341,7 +343,7 @@ export function composeRuntimePromptWithReceipt(input: RuntimePromptCompositionI
|
|
|
341
343
|
const supplement = getTierPromptSupplement(tier);
|
|
342
344
|
const prompt = joinPromptParts(
|
|
343
345
|
input.runtimePrompt,
|
|
344
|
-
|
|
346
|
+
buildVibeProjectionPrompt(input.memoryRegistry),
|
|
345
347
|
buildProjectContextPrompt(input.shellPaths),
|
|
346
348
|
input.operatorPolicy,
|
|
347
349
|
buildReviewedMemoryPrompt(input.memoryRegistry, { turnText: input.turnText }),
|
package/src/agent/vibe-file.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
3
4
|
import type { ShellPathService } from '@/runtime/index.ts';
|
|
4
5
|
import { GOODVIBES_AGENT_SURFACE_ROOT } from '../config/surface.ts';
|
|
@@ -9,7 +10,7 @@ import { parseMarkdownFrontmatter, stripMarkdownFrontmatter } from './markdown-f
|
|
|
9
10
|
// the same '## VIBE.md' block from those records (caveat preserved); the file is
|
|
10
11
|
// demoted to an import/export FORMAT folded in via vibeBodyToConstraintOptions.
|
|
11
12
|
import { renderVibeProjection, vibeBodyToConstraintOptions } from '@pellux/goodvibes-sdk/platform/state';
|
|
12
|
-
import type { MemoryRegistry, MemoryScope } from '@pellux/goodvibes-sdk/platform/state';
|
|
13
|
+
import type { MemoryRecord, MemoryRegistry, MemoryScope } from '@pellux/goodvibes-sdk/platform/state';
|
|
13
14
|
|
|
14
15
|
export type AgentVibeScope = 'project' | 'global';
|
|
15
16
|
|
|
@@ -220,8 +221,8 @@ export function buildVibePrompt(shellPaths: AgentVibePaths): string | null {
|
|
|
220
221
|
* store, so persona instructions have a single source of truth alongside every other
|
|
221
222
|
* durable fact. Returns null when there are no persona records to project.
|
|
222
223
|
*/
|
|
223
|
-
export function buildVibeProjectionPrompt(
|
|
224
|
-
return renderVibeProjection(
|
|
224
|
+
export function buildVibeProjectionPrompt(memoryRecords: { getAll(): readonly MemoryRecord[] }): string | null {
|
|
225
|
+
return renderVibeProjection(memoryRecords.getAll());
|
|
225
226
|
}
|
|
226
227
|
|
|
227
228
|
/**
|
|
@@ -255,6 +256,86 @@ export async function importVibeFilesIntoMemory(
|
|
|
255
256
|
return created;
|
|
256
257
|
}
|
|
257
258
|
|
|
259
|
+
/**
|
|
260
|
+
* W6-C2 (E6): the persisted marker that makes the VIBE.md → memory import a strictly
|
|
261
|
+
* ONE-TIME migration. Keyed by absolute file path → content hash, so importing the same
|
|
262
|
+
* VIBE.md twice is a no-op (re-import would create near-duplicate persona records), while
|
|
263
|
+
* a NEW project's VIBE.md still migrates exactly once. Mirrors the sessions.spine-folded
|
|
264
|
+
* marker precedent (bootstrap.ts) — a small JSON sidecar, read before and written after.
|
|
265
|
+
*/
|
|
266
|
+
interface VibeImportMarker {
|
|
267
|
+
readonly migrated: Record<string, string>;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function vibeImportMarkerPath(shellPaths: Pick<ShellPathService, 'resolveUserPath'>): string {
|
|
271
|
+
return shellPaths.resolveUserPath(GOODVIBES_AGENT_SURFACE_ROOT, 'vibe-import.migrated.json');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function readVibeImportMarker(path: string): VibeImportMarker {
|
|
275
|
+
if (!existsSync(path)) return { migrated: {} };
|
|
276
|
+
try {
|
|
277
|
+
const parsed = JSON.parse(readFileSync(path, 'utf-8')) as unknown;
|
|
278
|
+
const migrated = (parsed as { migrated?: unknown })?.migrated;
|
|
279
|
+
if (migrated && typeof migrated === 'object' && !Array.isArray(migrated)) {
|
|
280
|
+
const out: Record<string, string> = {};
|
|
281
|
+
for (const [key, value] of Object.entries(migrated as Record<string, unknown>)) {
|
|
282
|
+
if (typeof value === 'string') out[key] = value;
|
|
283
|
+
}
|
|
284
|
+
return { migrated: out };
|
|
285
|
+
}
|
|
286
|
+
} catch {
|
|
287
|
+
// Corrupt marker → treat as "nothing migrated"; the path+hash guard still prevents
|
|
288
|
+
// duplicating any record that was already imported in a prior clean run.
|
|
289
|
+
}
|
|
290
|
+
return { migrated: {} };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function writeVibeImportMarker(path: string, marker: VibeImportMarker): void {
|
|
294
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
295
|
+
writeFileSync(path, `${JSON.stringify(marker, null, 2)}\n`, 'utf-8');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function hashVibeBody(body: string): string {
|
|
299
|
+
return createHash('sha256').update(body, 'utf-8').digest('hex');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* W6-C2 (E6): fold discovered VIBE.md files into persona/constraint records ONCE.
|
|
304
|
+
* Guarded by a persisted path→hash marker so it never re-imports the same file (which
|
|
305
|
+
* would create near-duplicate persona records). Called at boot AFTER memoryStore.init().
|
|
306
|
+
* Returns the number of records created this run (0 when everything is already migrated).
|
|
307
|
+
*/
|
|
308
|
+
export async function importVibeFilesIntoMemoryOnce(
|
|
309
|
+
memoryRegistry: MemoryRegistry,
|
|
310
|
+
shellPaths: AgentVibePaths,
|
|
311
|
+
): Promise<number> {
|
|
312
|
+
const markerPath = vibeImportMarkerPath(shellPaths);
|
|
313
|
+
const marker = readVibeImportMarker(markerPath);
|
|
314
|
+
const snapshot = discoverVibeFiles(shellPaths);
|
|
315
|
+
let created = 0;
|
|
316
|
+
let changed = false;
|
|
317
|
+
for (const file of snapshot.files) {
|
|
318
|
+
const key = resolve(file.path);
|
|
319
|
+
const hash = hashVibeBody(file.body);
|
|
320
|
+
if (marker.migrated[key] === hash) continue; // already migrated this exact content
|
|
321
|
+
const scope: MemoryScope = file.scope === 'global' ? 'team' : 'project';
|
|
322
|
+
const name = file.frontmatter.name?.trim();
|
|
323
|
+
const options = vibeBodyToConstraintOptions(file.body, {
|
|
324
|
+
scope,
|
|
325
|
+
...(name ? { name } : {}),
|
|
326
|
+
sourceRef: file.path,
|
|
327
|
+
});
|
|
328
|
+
for (const opts of options) {
|
|
329
|
+
await memoryRegistry.add(opts);
|
|
330
|
+
created += 1;
|
|
331
|
+
}
|
|
332
|
+
marker.migrated[key] = hash;
|
|
333
|
+
changed = true;
|
|
334
|
+
}
|
|
335
|
+
if (changed) writeVibeImportMarker(markerPath, marker);
|
|
336
|
+
return created;
|
|
337
|
+
}
|
|
338
|
+
|
|
258
339
|
export function formatVibeStatus(snapshot: AgentVibeSnapshot): string {
|
|
259
340
|
const lines: string[] = [
|
|
260
341
|
'GoodVibes Agent VIBE.md',
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* credential-status.ts — client-side, secret-FREE credential STATUS read.
|
|
3
|
+
*
|
|
4
|
+
* W6-C1 (E7 config sharing). When GoodVibes Agent acts as a CLIENT of an adopted
|
|
5
|
+
* external daemon, it reads credential *status* (configured / usable) from the
|
|
6
|
+
* daemon's shared store over the wire — the `credentials.get` operator method
|
|
7
|
+
* (GET /config/credentials, admin + read:config). It NEVER receives raw secret
|
|
8
|
+
* bytes: the wire contract (CREDENTIALS_SNAPSHOT_SCHEMA) carries status metadata
|
|
9
|
+
* only. This is a VISIBILITY path, not secret transport.
|
|
10
|
+
*
|
|
11
|
+
* Host-vs-client (see the SDK decision record 2026-07-06-config-sharing): a surface
|
|
12
|
+
* that IS the daemon host reads its own local SecretsManager directly (no wire hop —
|
|
13
|
+
* src/config/secrets.ts). A surface acting as a daemon client reads STATUS here.
|
|
14
|
+
* Secret RESOLUTION (the value provider auth needs) always stays local/env; only the
|
|
15
|
+
* status read moves to the daemon path.
|
|
16
|
+
*
|
|
17
|
+
* The degrade contract mirrors goodvibes-webui v1.0.1 `deriveCredentialAvailability`
|
|
18
|
+
* exactly: a 503 CREDENTIAL_STORE_UNAVAILABLE (by machine code), a METHOD_NOT_FOUND
|
|
19
|
+
* from an older daemon, or any transport failure yields an honest reason-carrying
|
|
20
|
+
* `available: false` — NEVER a fabricated "configured", NEVER a surfaced secret byte.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** One credential's status metadata from the daemon's shared store — never bytes. */
|
|
24
|
+
export interface CredentialStatusEntry {
|
|
25
|
+
readonly key: string;
|
|
26
|
+
readonly configured: boolean;
|
|
27
|
+
readonly usable: boolean;
|
|
28
|
+
readonly source?: string;
|
|
29
|
+
readonly scope?: string;
|
|
30
|
+
readonly secure?: boolean;
|
|
31
|
+
readonly overriddenByEnv?: boolean;
|
|
32
|
+
readonly refSource?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type CredentialAvailability =
|
|
36
|
+
| { readonly available: true; readonly credentials: readonly CredentialStatusEntry[] }
|
|
37
|
+
| { readonly available: false; readonly reason: string };
|
|
38
|
+
|
|
39
|
+
/** Minimal connection shape for the daemon-client status read. */
|
|
40
|
+
export interface CredentialStatusConnection {
|
|
41
|
+
readonly baseUrl: string;
|
|
42
|
+
readonly token: string | null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
|
|
46
|
+
|
|
47
|
+
const CREDENTIALS_ROUTE = '/config/credentials';
|
|
48
|
+
const CREDENTIAL_STATUS_TIMEOUT_MS = 1500;
|
|
49
|
+
|
|
50
|
+
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
51
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function firstString(record: Record<string, unknown>, keys: readonly string[]): string {
|
|
55
|
+
for (const key of keys) {
|
|
56
|
+
const item = record[key];
|
|
57
|
+
if (typeof item === 'string' && item.trim()) return item;
|
|
58
|
+
}
|
|
59
|
+
return '';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Fold a `credentials.get` outcome into an honest availability value. Pure — this is
|
|
64
|
+
* the exact degrade contract mirrored from webui v1.0.1: keyed on the MACHINE CODE, it
|
|
65
|
+
* never fabricates "configured" and never carries a secret value. A malformed body (no
|
|
66
|
+
* credentials array) is treated as unavailable, not silently empty-but-configured.
|
|
67
|
+
*/
|
|
68
|
+
export function deriveCredentialAvailability(
|
|
69
|
+
outcome: { ok: true; value: unknown } | { ok: false; error: unknown },
|
|
70
|
+
): CredentialAvailability {
|
|
71
|
+
if (!outcome.ok) {
|
|
72
|
+
const err = asRecord(outcome.error);
|
|
73
|
+
const code = err ? firstString(err, ['code']) : '';
|
|
74
|
+
if (code === 'CREDENTIAL_STORE_UNAVAILABLE') {
|
|
75
|
+
return { available: false, reason: 'The daemon has no shared credential store wired.' };
|
|
76
|
+
}
|
|
77
|
+
if (code === 'METHOD_NOT_FOUND' || code === 'NOT_INVOKABLE') {
|
|
78
|
+
return { available: false, reason: 'This daemon does not serve credential status yet.' };
|
|
79
|
+
}
|
|
80
|
+
return { available: false, reason: 'Credential status unavailable right now.' };
|
|
81
|
+
}
|
|
82
|
+
const value = asRecord(outcome.value);
|
|
83
|
+
const raw = value?.credentials;
|
|
84
|
+
if (!Array.isArray(raw)) return { available: false, reason: 'Credential status unavailable right now.' };
|
|
85
|
+
const credentials: CredentialStatusEntry[] = [];
|
|
86
|
+
for (const item of raw) {
|
|
87
|
+
const rec = asRecord(item);
|
|
88
|
+
const key = rec ? firstString(rec, ['key']) : '';
|
|
89
|
+
if (!rec || !key) continue;
|
|
90
|
+
credentials.push({
|
|
91
|
+
key,
|
|
92
|
+
configured: rec.configured === true,
|
|
93
|
+
usable: rec.usable === true,
|
|
94
|
+
source: firstString(rec, ['source']) || undefined,
|
|
95
|
+
scope: firstString(rec, ['scope']) || undefined,
|
|
96
|
+
secure: rec.secure === true ? true : rec.secure === false ? false : undefined,
|
|
97
|
+
overriddenByEnv: rec.overriddenByEnv === true ? true : rec.overriddenByEnv === false ? false : undefined,
|
|
98
|
+
refSource: firstString(rec, ['refSource']) || undefined,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return { available: true, credentials };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Extract the machine code from a non-2xx daemon response body, falling back to the
|
|
106
|
+
* HTTP status for legacy daemons that predate the code field. A 404 with no code is
|
|
107
|
+
* treated as METHOD_NOT_FOUND (older daemon without the route — same as webui's
|
|
108
|
+
* isMethodUnavailableError); a 503 with no code as CREDENTIAL_STORE_UNAVAILABLE.
|
|
109
|
+
*/
|
|
110
|
+
function machineCodeFromResponse(body: unknown, status: number): string {
|
|
111
|
+
const record = asRecord(body);
|
|
112
|
+
const direct = record ? firstString(record, ['code']) : '';
|
|
113
|
+
if (direct) return direct;
|
|
114
|
+
const nested = record ? asRecord(record.error) : null;
|
|
115
|
+
const nestedCode = nested ? firstString(nested, ['code']) : '';
|
|
116
|
+
if (nestedCode) return nestedCode;
|
|
117
|
+
if (status === 404) return 'METHOD_NOT_FOUND';
|
|
118
|
+
if (status === 503) return 'CREDENTIAL_STORE_UNAVAILABLE';
|
|
119
|
+
return '';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Read credential status from the connected daemon as a client, honestly degraded.
|
|
124
|
+
* Status only — the response is folded through {@link deriveCredentialAvailability},
|
|
125
|
+
* so a down store, an older daemon, an absent token, or a transport failure all become
|
|
126
|
+
* a reason-carrying `available: false`, never a fabricated "configured" and never a
|
|
127
|
+
* secret byte. `key` narrows to a single credential (the daemon's caller-named probe).
|
|
128
|
+
*/
|
|
129
|
+
export async function fetchDaemonCredentialAvailability(
|
|
130
|
+
connection: CredentialStatusConnection,
|
|
131
|
+
options: { readonly key?: string; readonly fetchImpl?: FetchLike } = {},
|
|
132
|
+
): Promise<CredentialAvailability> {
|
|
133
|
+
if (!connection.token) {
|
|
134
|
+
return { available: false, reason: 'No connected-host operator token; credential status is unavailable.' };
|
|
135
|
+
}
|
|
136
|
+
const fetchImpl = options.fetchImpl ?? (globalThis.fetch as FetchLike | undefined);
|
|
137
|
+
if (!fetchImpl) {
|
|
138
|
+
return { available: false, reason: 'Credential status unavailable right now.' };
|
|
139
|
+
}
|
|
140
|
+
const query = options.key ? `?key=${encodeURIComponent(options.key)}` : '';
|
|
141
|
+
const url = `${connection.baseUrl}${CREDENTIALS_ROUTE}${query}`;
|
|
142
|
+
try {
|
|
143
|
+
const response = await fetchImpl(url, {
|
|
144
|
+
method: 'GET',
|
|
145
|
+
headers: { authorization: `Bearer ${connection.token}` },
|
|
146
|
+
signal: AbortSignal.timeout(CREDENTIAL_STATUS_TIMEOUT_MS),
|
|
147
|
+
});
|
|
148
|
+
const text = await response.text();
|
|
149
|
+
let body: unknown = text;
|
|
150
|
+
try {
|
|
151
|
+
body = text.trim() ? JSON.parse(text) as unknown : {};
|
|
152
|
+
} catch {
|
|
153
|
+
body = text;
|
|
154
|
+
}
|
|
155
|
+
if (!response.ok) {
|
|
156
|
+
return deriveCredentialAvailability({
|
|
157
|
+
ok: false,
|
|
158
|
+
error: { code: machineCodeFromResponse(body, response.status), status: response.status },
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return deriveCredentialAvailability({ ok: true, value: body });
|
|
162
|
+
} catch (error) {
|
|
163
|
+
return deriveCredentialAvailability({ ok: false, error });
|
|
164
|
+
}
|
|
165
|
+
}
|
package/src/config/index.ts
CHANGED
|
@@ -55,3 +55,15 @@ export function getConfiguredSystemPrompt(configManager: Pick<ConfigManager, 'ge
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
export { getConfiguredApiKeys, resolveApiKeys } from '@pellux/goodvibes-sdk/platform/config';
|
|
58
|
+
|
|
59
|
+
// W6-C1 (E7): the daemon-client credential STATUS read (secret-free, honest-degrade).
|
|
60
|
+
// Value reads above stay local/env; only the status VISIBILITY path moves to the daemon.
|
|
61
|
+
export {
|
|
62
|
+
deriveCredentialAvailability,
|
|
63
|
+
fetchDaemonCredentialAvailability,
|
|
64
|
+
} from './credential-status.ts';
|
|
65
|
+
export type {
|
|
66
|
+
CredentialAvailability,
|
|
67
|
+
CredentialStatusConnection,
|
|
68
|
+
CredentialStatusEntry,
|
|
69
|
+
} from './credential-status.ts';
|
package/src/config/secrets.ts
CHANGED
|
@@ -16,6 +16,23 @@ import {
|
|
|
16
16
|
import { isSecretRefInput } from '@pellux/goodvibes-sdk/platform/config';
|
|
17
17
|
import { GOODVIBES_AGENT_SURFACE_ROOT } from './surface.ts';
|
|
18
18
|
|
|
19
|
+
// W6-C1 host-vs-client split (E7 config sharing): this SecretsManager is the LOCAL-HOST
|
|
20
|
+
// read path — pinned to GOODVIBES_AGENT_SURFACE_ROOT, it resolves secret VALUES from the
|
|
21
|
+
// surface store/env for provider auth, unchanged. When the Agent acts as a CLIENT of an
|
|
22
|
+
// adopted external daemon, credential *status* (configured/usable — never bytes) is read
|
|
23
|
+
// over the wire via ./credential-status.ts (`fetchDaemonCredentialAvailability`), which
|
|
24
|
+
// degrades honestly and never fabricates "configured". Only STATUS visibility moves to
|
|
25
|
+
// the daemon path; value resolution stays here, local and env-only for API keys.
|
|
26
|
+
export {
|
|
27
|
+
deriveCredentialAvailability,
|
|
28
|
+
fetchDaemonCredentialAvailability,
|
|
29
|
+
} from './credential-status.ts';
|
|
30
|
+
export type {
|
|
31
|
+
CredentialAvailability,
|
|
32
|
+
CredentialStatusConnection,
|
|
33
|
+
CredentialStatusEntry,
|
|
34
|
+
} from './credential-status.ts';
|
|
35
|
+
|
|
19
36
|
export type SecretsManagerOptions = Omit<SdkSecretsManagerOptions, 'surfaceRoot'>;
|
|
20
37
|
|
|
21
38
|
const RAW_SECRET_LITERAL_PREFIX = '__GOODVIBES_LITERAL_V1__';
|
|
@@ -26,7 +26,10 @@ import {
|
|
|
26
26
|
} from '@/runtime/index.ts';
|
|
27
27
|
import { loadBootstrapSystemPrompt, syncConfiguredServices } from '@/runtime/index.ts';
|
|
28
28
|
import { registerBootstrapHookBridge } from '@/runtime/index.ts';
|
|
29
|
-
import { createRuntimeServices, type RuntimeServices } from './services.ts';
|
|
29
|
+
import { createRuntimeServices, foldAgentLegacyMemory, type RuntimeServices } from './services.ts';
|
|
30
|
+
import { formatMemoryFoldReport } from '@pellux/goodvibes-sdk/platform/state';
|
|
31
|
+
import { summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
32
|
+
import { importVibeFilesIntoMemoryOnce } from '../agent/vibe-file.ts';
|
|
30
33
|
import { createUiRuntimeServices, type UiRuntimeServices } from './ui-services.ts';
|
|
31
34
|
import { installAgentToolPolicyGuard } from '../tools/agent-tool-policy-guard.ts';
|
|
32
35
|
import { registerAgentChannelSendTool } from '../tools/agent-channel-send-tool.ts';
|
|
@@ -345,6 +348,34 @@ export async function initializeBootstrapCore(
|
|
|
345
348
|
memoryStore.close();
|
|
346
349
|
});
|
|
347
350
|
|
|
351
|
+
// W6-C2 (E6): fold the agent's legacy per-surface memory into the canonical
|
|
352
|
+
// cross-surface store (id-keyed, idempotent, never deletes the legacy file) and
|
|
353
|
+
// SURFACE the fold report — migration honesty requires that what moved is visible,
|
|
354
|
+
// not silently swallowed. Non-fatal: a fold failure must never block startup.
|
|
355
|
+
try {
|
|
356
|
+
const foldReport = await foldAgentLegacyMemory(
|
|
357
|
+
services.memoryStore,
|
|
358
|
+
services.memoryEmbeddingRegistry,
|
|
359
|
+
services.shellPaths,
|
|
360
|
+
);
|
|
361
|
+
logger.info('agent legacy memory fold report', { report: formatMemoryFoldReport(foldReport) });
|
|
362
|
+
} catch (err) {
|
|
363
|
+
logger.warn('agent legacy memory fold failed (non-fatal)', { error: summarizeError(err) });
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// W6-C2 (E6): ONE-TIME migration of discovered VIBE.md files into persona/constraint
|
|
367
|
+
// records, guarded by a persisted path→hash marker so it never re-imports the same
|
|
368
|
+
// file (which would create near-duplicate persona records). After this, the VIBE
|
|
369
|
+
// prompt is a PROJECTION of those records (buildVibeProjectionPrompt).
|
|
370
|
+
try {
|
|
371
|
+
const importedPersona = await importVibeFilesIntoMemoryOnce(services.memoryRegistry, services.shellPaths);
|
|
372
|
+
if (importedPersona > 0) {
|
|
373
|
+
logger.info('agent VIBE.md persona migration', { records: importedPersona });
|
|
374
|
+
}
|
|
375
|
+
} catch (err) {
|
|
376
|
+
logger.warn('agent VIBE.md persona migration failed (non-fatal)', { error: summarizeError(err) });
|
|
377
|
+
}
|
|
378
|
+
|
|
348
379
|
const renderRequestRef = { value: (): void => {} };
|
|
349
380
|
// R1: Coalescing render scheduler — collapses N same-microtask requestRender() calls into 1.
|
|
350
381
|
// Also enforces a 16ms minimum interval to cap at ~60fps during streaming.
|
|
@@ -6,7 +6,7 @@ import { AgentPersonaRegistry, buildActivePersonaPrompt } from '../agent/persona
|
|
|
6
6
|
import { buildProjectContextPrompt, discoverProjectContextFiles } from '../agent/project-context-files.ts';
|
|
7
7
|
import { AgentRoutineRegistry, buildEnabledRoutinesPrompt, evaluateAgentRoutineReadiness } from '../agent/routine-registry.ts';
|
|
8
8
|
import { AgentSkillRegistry, buildEnabledSkillsPrompt, evaluateAgentSkillBundleReadiness, evaluateAgentSkillReadiness } from '../agent/skill-registry.ts';
|
|
9
|
-
import {
|
|
9
|
+
import { buildVibeProjectionPrompt, discoverVibeFiles } from '../agent/vibe-file.ts';
|
|
10
10
|
import type { PromptContextReceipt } from '../agent/prompt-context-receipts.ts';
|
|
11
11
|
import type { CommandContext } from '../input/command-registry.ts';
|
|
12
12
|
import { previewHarnessText } from './agent-harness-text.ts';
|
|
@@ -399,7 +399,10 @@ function promptContextSegments(context: CommandContext, includeParameters: boole
|
|
|
399
399
|
}
|
|
400
400
|
|
|
401
401
|
const vibe = discoverVibeFiles(shellPaths);
|
|
402
|
-
|
|
402
|
+
// W6-C2 (E6): mirror the runtime — the VIBE prompt is a PROJECTION of persona records,
|
|
403
|
+
// not a file re-read (discoverVibeFiles stays for the file-discovery receipt below).
|
|
404
|
+
const vibeMemory = promptMemoryApi(context.clients?.agentKnowledgeApi?.memory);
|
|
405
|
+
const vibePrompt = (vibeMemory ? buildVibeProjectionPrompt(vibeMemory) : null) ?? '';
|
|
403
406
|
const projectContext = discoverProjectContextFiles(shellPaths);
|
|
404
407
|
const projectContextPrompt = buildProjectContextPrompt(shellPaths) ?? '';
|
|
405
408
|
const personaSnapshot = AgentPersonaRegistry.fromShellPaths(shellPaths).snapshot();
|
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 = '1.5.
|
|
9
|
+
let _version = '1.5.7';
|
|
10
10
|
try {
|
|
11
11
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', 'package.json'), 'utf-8')) as {
|
|
12
12
|
readonly version?: unknown;
|