@adonis-agora/agent
Version:
Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.
120 lines • 5.6 kB
JavaScript
/**
* Recognizes complete, explicit decision messages. This grants no authority: callers must
* resolve the authenticated actor and use the normal scoped decision service with via=text.
* Never feed model output here or consume an unmatched message as an approval.
*/
export function resolveTextActionProposalDecision(text, proposals, vocabulary) {
const command = parseTextActionProposalCommand(text, vocabulary);
if (command.status === 'unmatched')
return command;
const { decision, remember } = command;
const pending = proposals.filter((proposal) => proposal.decision === 'pending');
const explicitId = command.proposalId;
if (explicitId !== undefined) {
if (!pending.some((proposal) => proposal.id === explicitId))
return { status: 'unmatched' };
return { status: 'decision', proposalId: explicitId, decision, remember };
}
if (pending.length === 0)
return { status: 'unmatched' };
if (pending.length > 1)
return { status: 'ambiguous', proposalIds: pending.map(({ id }) => id) };
const candidate = pending[0];
if (!candidate)
return { status: 'unmatched' };
return { status: 'decision', proposalId: candidate.id, decision, remember };
}
/** English. Portuguese ships as {@link ptBrActionProposalText}; any other language via config. */
export const DEFAULT_TEXT_ACTION_PROPOSAL_VOCABULARY = {
approve: ['yes', 'confirm', 'approve', 'approved', 'ok'],
reject: ['no', 'cancel', 'reject', 'deny'],
remember: ['always in this conversation'],
};
export const DEFAULT_TEXT_ACTION_PROPOSAL_REPLIES = {
approved: 'Proposal approved and queued to run.',
rejected: 'Proposal rejected; nothing was run.',
expired: 'The proposal expired; nothing was run.',
unchanged: 'This proposal could not be changed; refresh the list to see where it stands.',
ambiguous: (ids) => `Which proposal? Reply confirm #ID or cancel #ID: ${ids.join(', ')}`,
tooMany: 'There are several proposals. Confirm or reject one with an explicit #ID.',
};
/** The reply to a decision the store answered with `result`. */
export function textActionProposalReply(result, decision, replies = DEFAULT_TEXT_ACTION_PROPOSAL_REPLIES) {
return result.status === 'expired' || result.proposal?.decision === 'expired'
? replies.expired
: result.status === 'applied' || result.status === 'unchanged'
? decision === 'approved'
? replies.approved
: replies.rejected
: replies.unchanged;
}
const escapeRegExp = (word) => word.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const alternatives = (words) => words
.map((word) => word.trim())
.filter((word) => word.length > 0)
.map((word) => escapeRegExp(word).replace(/\s+/g, '\\s+'))
.join('|');
const fold = (word) => word.trim().toLowerCase();
/** Parse exact consent syntax only. Authentication and target lookup remain the caller's responsibility. */
export function parseTextActionProposalCommand(text, vocabulary = DEFAULT_TEXT_ACTION_PROPOSAL_VOCABULARY) {
if (/[\r\n]/u.test(text))
return { status: 'unmatched' };
const verbs = alternatives([...vocabulary.approve, ...vocabulary.reject]);
if (verbs.length === 0)
return { status: 'unmatched' };
const remember = alternatives(vocabulary.remember);
const match = new RegExp(`^(${verbs})${remember.length > 0 ? `(?: (${remember}))?` : '()'}(?: #([^\\s]+?))?[.!]?$`, 'iu').exec(text.trim());
if (!match)
return { status: 'unmatched' };
const verb = match[1] === undefined ? undefined : fold(match[1]);
if (verb === undefined)
return { status: 'unmatched' };
// A word on both lists is read as a refusal: never infer consent from an ambiguous word.
const decision = vocabulary.reject.some((word) => fold(word) === verb) ? 'rejected' : 'approved';
const remembers = match[2] !== undefined && match[2] !== '';
if (remembers && decision !== 'approved')
return { status: 'unmatched' };
return {
status: 'command',
decision,
remember: remembers,
...(match[3] !== undefined ? { proposalId: match[3] } : {}),
};
}
/**
* Brazilian Portuguese text decisions — `config/agent.ts`: `actionProposalText: ptBrActionProposalText`.
* Commands in Portuguese (English ones still work), replies in Portuguese.
*/
export const ptBrActionProposalText = {
vocabulary: {
language: 'pt-BR',
approve: [
'sim',
'confirmo',
'confirmar',
'aprovar',
'aprovo',
'pode',
...DEFAULT_TEXT_ACTION_PROPOSAL_VOCABULARY.approve,
],
reject: [
'não',
'nao',
'cancelar',
'cancela',
'rejeitar',
'rejeito',
...DEFAULT_TEXT_ACTION_PROPOSAL_VOCABULARY.reject,
],
remember: ['sempre nesta conversa', ...DEFAULT_TEXT_ACTION_PROPOSAL_VOCABULARY.remember],
},
replies: {
approved: 'Confirmado! Já estou cuidando disso.',
rejected: 'Tudo bem, cancelado. Nada foi feito.',
expired: 'Esse pedido expirou; nada foi feito.',
unchanged: 'Não foi possível alterar esta proposta; atualize a lista para consultar seu estado.',
ambiguous: (ids) => `Qual proposta? Responda confirmar #ID ou cancelar #ID: ${ids.join(', ')}`,
tooMany: 'Há várias propostas. Confirme ou rejeite usando #ID explícito.',
},
};
//# sourceMappingURL=action-proposal-text.js.map