264 lines
8.8 KiB
JavaScript
264 lines
8.8 KiB
JavaScript
/**
|
|
* Grok-class agent capability pack (ACP-shaped events over NMH).
|
|
*/
|
|
|
|
const sandbox = require('../agent/sandbox.js');
|
|
const sessions = require('../agent/sessions.js');
|
|
const loop = require('../agent/loop.js');
|
|
const mcp = require('../agent/mcp.js');
|
|
const customTools = require('../agent/custom-tools.js');
|
|
const toolSet = require('../agent/tool-set.js');
|
|
const qvacEngine = require('../qvac/engine.js');
|
|
|
|
function makeJobId(prefix) {
|
|
return prefix + '_' + Date.now() + '_' + Math.random().toString(36).slice(2, 10);
|
|
}
|
|
|
|
function refuseDisabled(ctx) {
|
|
if (qvacEngine.isUserEnabled()) return false;
|
|
ctx.reply({ ok: false, error: qvacEngine.disabledError().message });
|
|
return true;
|
|
}
|
|
|
|
function normalizePermDecision(p) {
|
|
p = p || {};
|
|
if (p.decision === 'always' || p.always === true) return 'always';
|
|
if (p.decision === 'deny' || p.allow === false) return 'deny';
|
|
if (p.decision === 'allow' || p.allow === true) return 'allow';
|
|
return 'deny';
|
|
}
|
|
|
|
function createAgentPack() {
|
|
const commands = {
|
|
async setGrants(ctx) {
|
|
sandbox.setGrants(ctx.payload || {});
|
|
ctx.reply({ ok: true, roots: sandbox.listGrantedRoots() });
|
|
},
|
|
async create(ctx) {
|
|
if (refuseDisabled(ctx)) return;
|
|
const p = ctx.payload || {};
|
|
const origin = p._origin || '';
|
|
const hostWorkspace = toolSet.parseHostWorkspace(p);
|
|
let cwd;
|
|
let workspaceLabel;
|
|
if (!hostWorkspace) {
|
|
workspaceLabel = String(p.workspace || p.cwd || 'page').slice(0, 200);
|
|
if (!workspaceLabel || workspaceLabel === 'default') workspaceLabel = 'page';
|
|
cwd = workspaceLabel;
|
|
} else {
|
|
cwd = p.cwd && p.cwd !== 'default' ? sandbox.resolvePath(origin, p.cwd) : sandbox.defaultCwd(origin);
|
|
workspaceLabel = cwd;
|
|
}
|
|
const summary = sessions.create({
|
|
origin,
|
|
cwd,
|
|
workspace: workspaceLabel,
|
|
hostWorkspace,
|
|
builtinTools: p.builtinTools === false ? false : Array.isArray(p.builtinTools) ? p.builtinTools : undefined,
|
|
model: p.model || 'qwen3.5-4b',
|
|
title: p.title || 'New session',
|
|
sessionId: p.sessionId,
|
|
goal: p.goal ? require('../agent/goal.js').create(p.goal, { verify: p.verify !== false }) : null,
|
|
planMode: p.planMode
|
|
? { state: p.planMode === true || p.planMode === 'active' ? 'active' : 'inactive', planPath: 'plan.md' }
|
|
: undefined,
|
|
});
|
|
customTools.setSession(summary.id, { hostWorkspace });
|
|
if (p.tools) {
|
|
try {
|
|
customTools.register(summary.id, p.tools);
|
|
summary.customTools = customTools.list(summary.id);
|
|
sessions.saveSummary(summary);
|
|
} catch (err) {
|
|
ctx.reply({ ok: false, error: err.message });
|
|
return;
|
|
}
|
|
}
|
|
ctx.reply({
|
|
ok: true,
|
|
sessionId: summary.id,
|
|
cwd: summary.cwd,
|
|
workspace: summary.workspace,
|
|
hostWorkspace,
|
|
model: summary.model,
|
|
tools: customTools.list(summary.id),
|
|
});
|
|
},
|
|
async load(ctx) {
|
|
if (refuseDisabled(ctx)) return;
|
|
try {
|
|
const s = sessions.load(ctx.payload.sessionId);
|
|
customTools.setSession(s.id, { hostWorkspace: s.hostWorkspace !== false });
|
|
if (Array.isArray(s.customTools) && s.customTools.length) {
|
|
try {
|
|
customTools.register(s.id, s.customTools);
|
|
} catch (_) {}
|
|
}
|
|
ctx.reply({ ok: true, session: s });
|
|
} catch (err) {
|
|
ctx.reply({ ok: false, error: err.message });
|
|
}
|
|
},
|
|
async list(ctx) {
|
|
ctx.reply({ ok: true, sessions: sessions.list() });
|
|
},
|
|
async prompt(ctx) {
|
|
if (refuseDisabled(ctx)) return;
|
|
const { payload, reply, emit } = ctx;
|
|
const jobId = payload.jobId || makeJobId('prompt');
|
|
let session;
|
|
try {
|
|
session = sessions.load(payload.sessionId);
|
|
customTools.setSession(session.id, { hostWorkspace: session.hostWorkspace !== false });
|
|
if (Array.isArray(session.customTools) && session.customTools.length && !customTools.list(session.id).length) {
|
|
try {
|
|
customTools.register(session.id, session.customTools);
|
|
} catch (_) {}
|
|
}
|
|
} catch (err) {
|
|
reply({ ok: false, error: err.message });
|
|
return;
|
|
}
|
|
reply({ ok: true, jobId, sessionId: session.id, streaming: true, status: 'started' });
|
|
loop.markLive(session.id);
|
|
try {
|
|
const result = await loop.runTurn({
|
|
session,
|
|
userText: payload.text || payload.prompt || '',
|
|
emit,
|
|
jobId,
|
|
payload,
|
|
planMode: payload.planMode,
|
|
});
|
|
emit('cap-end', { pack: 'agent', jobId, sessionId: session.id, ...result });
|
|
} catch (err) {
|
|
emit('cap-error', { pack: 'agent', jobId, sessionId: session.id, message: err.message });
|
|
} finally {
|
|
loop.finishLive(session.id);
|
|
}
|
|
},
|
|
async cancel(ctx) {
|
|
if (refuseDisabled(ctx)) return;
|
|
loop.cancel(ctx.payload && ctx.payload.sessionId);
|
|
ctx.reply({ ok: true });
|
|
},
|
|
async permission(ctx) {
|
|
if (refuseDisabled(ctx)) return;
|
|
const p = ctx.payload || {};
|
|
loop.resolvePermission(p.jobId, p.toolCallId, normalizePermDecision(p));
|
|
ctx.reply({ ok: true });
|
|
},
|
|
async planDecision(ctx) {
|
|
if (refuseDisabled(ctx)) return;
|
|
const p = ctx.payload || {};
|
|
loop.resolvePlanDecision(p.sessionId, p.decision === 'approve' || p.approve === true ? 'approve' : 'reject');
|
|
ctx.reply({ ok: true });
|
|
},
|
|
async answer(ctx) {
|
|
if (refuseDisabled(ctx)) return;
|
|
const p = ctx.payload || {};
|
|
loop.resolveAsk(p.jobId, p.toolCallId, p.choice != null ? p.choice : p.answer);
|
|
ctx.reply({ ok: true });
|
|
},
|
|
async registerTools(ctx) {
|
|
if (refuseDisabled(ctx)) return;
|
|
try {
|
|
const p = ctx.payload || {};
|
|
try {
|
|
const s = sessions.load(p.sessionId);
|
|
customTools.setSession(s.id, { hostWorkspace: s.hostWorkspace !== false });
|
|
} catch (_) {}
|
|
const listed = customTools.register(p.sessionId, p.tools || p.tool || []);
|
|
try {
|
|
const s = sessions.load(p.sessionId);
|
|
s.customTools = listed;
|
|
sessions.saveSummary(s);
|
|
} catch (_) {}
|
|
ctx.reply({ ok: true, tools: listed });
|
|
} catch (err) {
|
|
ctx.reply({ ok: false, error: err.message });
|
|
}
|
|
},
|
|
async unregisterTools(ctx) {
|
|
if (refuseDisabled(ctx)) return;
|
|
const p = ctx.payload || {};
|
|
ctx.reply({ ok: true, tools: customTools.unregister(p.sessionId, p.name) });
|
|
},
|
|
async listTools(ctx) {
|
|
if (refuseDisabled(ctx)) return;
|
|
const p = ctx.payload || {};
|
|
ctx.reply({ ok: true, tools: customTools.list(p.sessionId) });
|
|
},
|
|
async toolResult(ctx) {
|
|
if (refuseDisabled(ctx)) return;
|
|
const p = ctx.payload || {};
|
|
const ok = loop.resolveCustomResult(p.jobId, p.toolCallId, {
|
|
result: p.result,
|
|
error: p.error,
|
|
});
|
|
ctx.reply({ ok: true, resolved: ok });
|
|
},
|
|
async mcpRegister(ctx) {
|
|
if (refuseDisabled(ctx)) return;
|
|
try {
|
|
const servers = await mcp.register(ctx.payload || {}, ctx.payload || {});
|
|
ctx.reply({ ok: true, servers });
|
|
} catch (err) {
|
|
ctx.reply({ ok: false, error: err.message });
|
|
}
|
|
},
|
|
async mcpList(ctx) {
|
|
if (refuseDisabled(ctx)) return;
|
|
ctx.reply({ ok: true, servers: mcp.list() });
|
|
},
|
|
async mcpUnregister(ctx) {
|
|
if (refuseDisabled(ctx)) return;
|
|
ctx.reply({ ok: true, servers: mcp.unregister(ctx.payload && ctx.payload.id) });
|
|
},
|
|
async destroy(ctx) {
|
|
const id = ctx.payload && ctx.payload.sessionId;
|
|
if (!id) {
|
|
ctx.reply({ ok: false, error: 'sessionId required' });
|
|
return;
|
|
}
|
|
loop.cancel(id);
|
|
loop.forget(id);
|
|
customTools.clear(id);
|
|
const removed = sessions.remove(id);
|
|
if (!loop.isLive() && !loop.isWaiting() && typeof qvacEngine.idleSoon === 'function') {
|
|
qvacEngine.idleSoon(15000);
|
|
}
|
|
ctx.reply({ ok: true, removed: !!removed, sessionId: id });
|
|
},
|
|
async status(ctx) {
|
|
ctx.reply({
|
|
ok: true,
|
|
enabled: qvacEngine.isUserEnabled(),
|
|
sessionCount: sessions.list().length,
|
|
liveCount: loop.liveCount(),
|
|
modelLoaded: !!(qvacEngine.getLoaded() && qvacEngine.getLoaded().modelId),
|
|
grantedRoots: sandbox.listGrantedRoots(),
|
|
mcpCount: mcp.count(),
|
|
});
|
|
},
|
|
};
|
|
|
|
return {
|
|
id: 'agent',
|
|
commands,
|
|
getPublicStatus() {
|
|
return {
|
|
enabled: qvacEngine.isUserEnabled(),
|
|
sessionCount: sessions.list().length,
|
|
liveCount: loop.liveCount(),
|
|
modelLoaded: !!(qvacEngine.getLoaded() && qvacEngine.getLoaded().modelId),
|
|
grantedRoots: sandbox.listGrantedRoots(),
|
|
mcpCount: mcp.count(),
|
|
};
|
|
},
|
|
cleanup() {},
|
|
};
|
|
}
|
|
|
|
module.exports = { createAgentPack };
|