fix: compile dist from source in Docker, fix ChatResult interface

- Dockerfile now runs tsc during build so committed dist/ is never stale
- ChatResult interface was missing history[] and memoryUpdates[] fields
- Re-add missing MemoryUpdate import in orchestrator.ts
- Rebuild dist/ with all new fields included

Made-with: Cursor
This commit is contained in:
2026-02-27 19:27:42 -08:00
parent 837b6e8b8d
commit d9368e4abd
14 changed files with 675 additions and 259 deletions

117
dist/tools.js vendored
View File

@@ -41,6 +41,45 @@ const cp = __importStar(require("child_process"));
const util = __importStar(require("util"));
const minimatch_1 = require("minimatch");
const execAsync = util.promisify(cp.exec);
// =============================================================================
// SECURITY GUARDRAILS — Protected VIBN Platform Resources
//
// These repos and Coolify resources belong to the Vibn platform itself.
// Agents must never be allowed to push code or trigger deployments here.
// Read-only operations (list, read file, get status) are still permitted
// so agents can observe the platform state, but all mutations are blocked.
// =============================================================================
/** Gitea repos that agents can NEVER push to, commit to, or write issues on. */
const PROTECTED_GITEA_REPOS = new Set([
'mark/vibn-frontend',
'mark/theia-code-os',
'mark/vibn-agent-runner',
'mark/vibn-api',
'mark/master-ai',
]);
/** Coolify project UUID for the VIBN platform — agents cannot deploy here. */
const PROTECTED_COOLIFY_PROJECT = 'f4owwggokksgw0ogo0844os0';
/**
* Specific Coolify app UUIDs that must never be deployed by an agent.
* This is a belt-and-suspenders check in case the project UUID filter is bypassed.
*/
const PROTECTED_COOLIFY_APPS = new Set([
'y4cscsc8s08c8808go0448s0', // vibn-frontend
'kggs4ogckc0w8ggwkkk88kck', // vibn-postgres
'o4wwck0g0c04wgoo4g4s0004', // gitea
]);
function assertGiteaWritable(repo) {
if (PROTECTED_GITEA_REPOS.has(repo)) {
throw new Error(`SECURITY: Repo "${repo}" is a protected Vibn platform repo. ` +
`Agents cannot push code or modify issues in this repository.`);
}
}
function assertCoolifyDeployable(appUuid) {
if (PROTECTED_COOLIFY_APPS.has(appUuid)) {
throw new Error(`SECURITY: App "${appUuid}" is a protected Vibn platform application. ` +
`Agents cannot trigger deployments for this application.`);
}
}
exports.ALL_TOOLS = [
{
name: 'read_file',
@@ -296,6 +335,23 @@ exports.ALL_TOOLS = [
},
required: ['app_name']
}
},
{
name: 'save_memory',
description: 'Persist an important fact about this project to long-term memory. Use this to save decisions, tech stack choices, feature descriptions, constraints, or goals so they are remembered across conversations.',
parameters: {
type: 'object',
properties: {
key: { type: 'string', description: 'Short unique label (e.g. "primary_language", "auth_strategy", "deploy_target")' },
type: {
type: 'string',
enum: ['tech_stack', 'decision', 'feature', 'goal', 'constraint', 'note'],
description: 'Category of the memory item'
},
value: { type: 'string', description: 'The fact to remember (1-3 sentences)' }
},
required: ['key', 'type', 'value']
}
}
];
// ---------------------------------------------------------------------------
@@ -452,6 +508,21 @@ async function gitCommitAndPush(message, ctx) {
const cwd = ctx.workspaceRoot;
const { apiUrl, apiToken, username } = ctx.gitea;
try {
// Check the remote URL before committing — block pushes to protected repos
let remoteCheck = '';
try {
remoteCheck = (await execAsync('git remote get-url origin', { cwd })).stdout.trim();
}
catch { /* ok */ }
for (const protectedRepo of PROTECTED_GITEA_REPOS) {
const repoPath = protectedRepo.replace('mark/', '');
if (remoteCheck.includes(`/${repoPath}`) || remoteCheck.includes(`/${repoPath}.git`)) {
return {
error: `SECURITY: This workspace is linked to a protected Vibn platform repo (${protectedRepo}). ` +
`Agents cannot push to platform repos. Only user project repos are writable.`
};
}
}
await execAsync('git add -A', { cwd });
await execAsync(`git commit -m "${message.replace(/"/g, '\\"')}"`, { cwd });
// Get current remote URL, strip any existing credentials, re-inject cleanly
@@ -493,7 +564,11 @@ async function coolifyFetch(path, ctx, method = 'GET', body) {
return res.json();
}
async function coolifyListProjects(ctx) {
return coolifyFetch('/projects', ctx);
const projects = await coolifyFetch('/projects', ctx);
if (!Array.isArray(projects))
return projects;
// Filter out the protected VIBN project entirely — agents don't need to see it
return projects.filter((p) => p.uuid !== PROTECTED_COOLIFY_PROJECT);
}
async function coolifyListApplications(projectUuid, ctx) {
const all = await coolifyFetch('/applications', ctx);
@@ -502,6 +577,15 @@ async function coolifyListApplications(projectUuid, ctx) {
return all.filter((a) => a.project_uuid === projectUuid);
}
async function coolifyDeploy(appUuid, ctx) {
assertCoolifyDeployable(appUuid);
// Also check the app belongs to the right project
const apps = await coolifyFetch('/applications', ctx);
if (Array.isArray(apps)) {
const app = apps.find((a) => a.uuid === appUuid);
if (app?.project_uuid === PROTECTED_COOLIFY_PROJECT) {
return { error: `SECURITY: App "${appUuid}" belongs to the protected Vibn project. Agents cannot deploy platform apps.` };
}
}
return coolifyFetch(`/applications/${appUuid}/deploy`, ctx, 'POST');
}
async function coolifyGetLogs(appUuid, ctx) {
@@ -525,12 +609,14 @@ async function giteaFetch(path, ctx, method = 'GET', body) {
return res.json();
}
async function giteaCreateIssue(repo, title, body, labels, ctx) {
assertGiteaWritable(repo);
return giteaFetch(`/repos/${repo}/issues`, ctx, 'POST', { title, body, labels });
}
async function giteaListIssues(repo, state, ctx) {
return giteaFetch(`/repos/${repo}/issues?state=${state}&limit=20`, ctx);
}
async function giteaCloseIssue(repo, issueNumber, ctx) {
assertGiteaWritable(repo);
return giteaFetch(`/repos/${repo}/issues/${issueNumber}`, ctx, 'PATCH', { state: 'closed' });
}
// ---------------------------------------------------------------------------
@@ -560,7 +646,10 @@ async function listRepos(ctx) {
headers: { 'Authorization': `token ${ctx.gitea.apiToken}` }
});
const data = await res.json();
return (data.data || []).map((r) => ({
return (data.data || [])
// Hide protected platform repos from agent's view entirely
.filter((r) => !PROTECTED_GITEA_REPOS.has(r.full_name))
.map((r) => ({
name: r.full_name,
description: r.description,
default_branch: r.default_branch,
@@ -571,9 +660,12 @@ async function listRepos(ctx) {
}
async function listAllIssues(repo, state, ctx) {
if (repo) {
if (PROTECTED_GITEA_REPOS.has(repo)) {
return { error: `SECURITY: "${repo}" is a protected Vibn platform repo. Agents cannot access its issues.` };
}
return giteaFetch(`/repos/${repo}/issues?state=${state}&limit=20`, ctx);
}
// Fetch across all repos
// Fetch across all non-protected repos
const repos = await listRepos(ctx);
const allIssues = [];
for (const r of repos.slice(0, 10)) {
@@ -595,7 +687,10 @@ async function listAllApps(ctx) {
const apps = await coolifyFetch('/applications', ctx);
if (!Array.isArray(apps))
return apps;
return apps.map((a) => ({
return apps
// Filter out apps that belong to the protected VIBN project
.filter((a) => a.project_uuid !== PROTECTED_COOLIFY_PROJECT && !PROTECTED_COOLIFY_APPS.has(a.uuid))
.map((a) => ({
uuid: a.uuid,
name: a.name,
fqdn: a.fqdn,
@@ -611,6 +706,9 @@ async function getAppStatus(appName, ctx) {
const app = apps.find((a) => a.name?.toLowerCase() === appName.toLowerCase() || a.uuid === appName);
if (!app)
return { error: `App "${appName}" not found` };
if (PROTECTED_COOLIFY_APPS.has(app.uuid) || app.project_uuid === PROTECTED_COOLIFY_PROJECT) {
return { error: `SECURITY: "${appName}" is a protected Vibn platform app. Status is not exposed to agents.` };
}
const logs = await coolifyFetch(`/applications/${app.uuid}/logs?limit=20`, ctx);
return { name: app.name, uuid: app.uuid, status: app.status, fqdn: app.fqdn, logs };
}
@@ -648,6 +746,10 @@ async function getJobStatus(jobId) {
return { error: `Failed to get job: ${err instanceof Error ? err.message : String(err)}` };
}
}
function saveMemory(key, type, value, ctx) {
ctx.memoryUpdates.push({ key, type, value });
return { saved: true, key, type };
}
async function deployApp(appName, ctx) {
const apps = await coolifyFetch('/applications', ctx);
if (!Array.isArray(apps))
@@ -655,6 +757,13 @@ async function deployApp(appName, ctx) {
const app = apps.find((a) => a.name?.toLowerCase() === appName.toLowerCase() || a.uuid === appName);
if (!app)
return { error: `App "${appName}" not found` };
// Block deployment to protected VIBN platform apps
if (PROTECTED_COOLIFY_APPS.has(app.uuid) || app.project_uuid === PROTECTED_COOLIFY_PROJECT) {
return {
error: `SECURITY: "${appName}" is a protected Vibn platform application. ` +
`Agents can only deploy user project apps, not platform infrastructure.`
};
}
const result = await fetch(`${ctx.coolify.apiUrl}/api/v1/deploy?uuid=${app.uuid}&force=false`, {
headers: { 'Authorization': `Bearer ${ctx.coolify.apiToken}` }
});