mirror of
https://github.com/domfelipe/mika-agent-assist.git
synced 2026-08-07 16:16:50 +00:00
Implement Mika runtime sync and go-live controls
This commit is contained in:
parent
cb54fa4666
commit
0df3befb67
29 changed files with 2153 additions and 94 deletions
23
supabase/functions/_shared/hermes-config.ts
Normal file
23
supabase/functions/_shared/hermes-config.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
export const DEFAULT_OLLAMA_PROVIDER = "ollama-cloud";
|
||||
export const DEFAULT_OLLAMA_MODEL = "gemma4:31b-cloud";
|
||||
|
||||
const LEGACY_MODEL_ALIASES: Record<string, string> = {
|
||||
"openrouter/google/gemma-4-27b-a4b-it": DEFAULT_OLLAMA_MODEL,
|
||||
"openrouter/google/gemma-4-31b-it": DEFAULT_OLLAMA_MODEL,
|
||||
"ollama-cloud/gemma4:31b-cloud": DEFAULT_OLLAMA_MODEL,
|
||||
};
|
||||
|
||||
export function normalizeOllamaModelSelection(value?: string | null): string {
|
||||
const trimmed = (value ?? "").trim();
|
||||
if (!trimmed) return DEFAULT_OLLAMA_MODEL;
|
||||
|
||||
const mapped = LEGACY_MODEL_ALIASES[trimmed];
|
||||
if (mapped) return mapped;
|
||||
|
||||
if (trimmed.includes("/") && trimmed.includes(":")) {
|
||||
const candidate = trimmed.split("/").pop()?.trim();
|
||||
if (candidate) return candidate;
|
||||
}
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
|
|
@ -4,15 +4,13 @@ const RAILWAY_GRAPHQL = "https://backboard.railway.app/graphql/v2";
|
|||
|
||||
/**
|
||||
* Start command padrão dos containers Hermes.
|
||||
* - Verifica HERMES_SUSPENDED no início: se true, dorme infinitamente (agente "pausado")
|
||||
* - Aplica HERMES_SOUL_OVERRIDE em /opt/data/SOUL.md se presente
|
||||
* - Inicia o gateway Hermes
|
||||
* - Encaminha para o entrypoint custom da imagem `hermes-agent-custom`
|
||||
* - O próprio entrypoint aplica SOUL.md, model/provider, STT/TTS e suspensão
|
||||
*
|
||||
* IMPORTANTE: este comando deve ser idêntico ao configurado nos serviços Railway
|
||||
* existentes. Para serviços antigos, atualize manualmente via UI/Agent do Railway.
|
||||
*/
|
||||
export const HERMES_START_COMMAND =
|
||||
`/bin/bash -c 'if [ "$HERMES_SUSPENDED" = "true" ]; then echo "Agent suspended" && sleep infinity; fi && if [ -n "$HERMES_SOUL_OVERRIDE" ]; then echo "$HERMES_SOUL_OVERRIDE" > /opt/data/SOUL.md; fi && /opt/hermes/docker/entrypoint.sh gateway run'`;
|
||||
export const HERMES_START_COMMAND = `/opt/hermes-custom/entrypoint.sh`;
|
||||
|
||||
export interface RailwayError {
|
||||
message: string;
|
||||
|
|
@ -308,6 +306,151 @@ export async function getServiceEnvironmentId(opts: {
|
|||
return (await getServiceContext(opts)).environmentId;
|
||||
}
|
||||
|
||||
export interface RailwayServiceDomainInfo {
|
||||
id: string;
|
||||
domain: string;
|
||||
suffix?: string | null;
|
||||
certificateStatus?: string | null;
|
||||
}
|
||||
|
||||
export async function listRailwayServiceDomains(opts: {
|
||||
token: string;
|
||||
serviceId: string;
|
||||
environmentId: string;
|
||||
projectId?: string | null;
|
||||
}): Promise<{ serviceDomains: RailwayServiceDomainInfo[]; customDomains: RailwayServiceDomainInfo[] }> {
|
||||
const query = `
|
||||
query Domains($environmentId: String!, $serviceId: String!, $projectId: String) {
|
||||
domains(environmentId: $environmentId, serviceId: $serviceId, projectId: $projectId) {
|
||||
serviceDomains {
|
||||
id
|
||||
domain
|
||||
suffix
|
||||
}
|
||||
customDomains {
|
||||
id
|
||||
domain
|
||||
status {
|
||||
certificateStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
const res = await railwayQuery<{
|
||||
domains: {
|
||||
serviceDomains?: { id: string; domain: string; suffix?: string | null }[];
|
||||
customDomains?: { id: string; domain: string; status?: { certificateStatus?: string | null } | null }[];
|
||||
};
|
||||
}>(
|
||||
query,
|
||||
{
|
||||
environmentId: opts.environmentId,
|
||||
serviceId: opts.serviceId,
|
||||
projectId: opts.projectId ?? null,
|
||||
},
|
||||
opts.token,
|
||||
);
|
||||
|
||||
if (res.errors?.length) {
|
||||
throw new Error(`domains query failed: ${JSON.stringify(res.errors)}`);
|
||||
}
|
||||
|
||||
const serviceDomains = (res.data?.domains?.serviceDomains ?? []).map((item) => ({
|
||||
id: item.id,
|
||||
domain: item.domain,
|
||||
suffix: item.suffix ?? null,
|
||||
certificateStatus: "ISSUED",
|
||||
}));
|
||||
|
||||
const customDomains = (res.data?.domains?.customDomains ?? []).map((item) => ({
|
||||
id: item.id,
|
||||
domain: item.domain,
|
||||
certificateStatus: item.status?.certificateStatus ?? null,
|
||||
}));
|
||||
|
||||
return { serviceDomains, customDomains };
|
||||
}
|
||||
|
||||
export async function createRailwayServiceDomain(opts: {
|
||||
token: string;
|
||||
serviceId: string;
|
||||
environmentId: string;
|
||||
targetPort?: number;
|
||||
}): Promise<RailwayServiceDomainInfo> {
|
||||
const mutation = `
|
||||
mutation ServiceDomainCreate($input: ServiceDomainCreateInput!) {
|
||||
serviceDomainCreate(input: $input) {
|
||||
id
|
||||
domain
|
||||
suffix
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const input: Record<string, unknown> = {
|
||||
serviceId: opts.serviceId,
|
||||
environmentId: opts.environmentId,
|
||||
};
|
||||
if (opts.targetPort !== undefined) {
|
||||
input.targetPort = opts.targetPort;
|
||||
}
|
||||
|
||||
const res = await railwayQuery<{
|
||||
serviceDomainCreate: { id: string; domain: string; suffix?: string | null };
|
||||
}>(mutation, { input }, opts.token);
|
||||
|
||||
if (res.errors?.length) {
|
||||
throw new Error(`serviceDomainCreate failed: ${JSON.stringify(res.errors)}`);
|
||||
}
|
||||
|
||||
const created = res.data?.serviceDomainCreate;
|
||||
if (!created?.domain) {
|
||||
throw new Error("serviceDomainCreate returned no domain");
|
||||
}
|
||||
|
||||
return {
|
||||
id: created.id,
|
||||
domain: created.domain,
|
||||
suffix: created.suffix ?? null,
|
||||
certificateStatus: "ISSUED",
|
||||
};
|
||||
}
|
||||
|
||||
export async function ensureRailwayServiceDomain(opts: {
|
||||
token: string;
|
||||
serviceId: string;
|
||||
environmentId: string;
|
||||
projectId?: string | null;
|
||||
targetPort?: number;
|
||||
}): Promise<RailwayServiceDomainInfo> {
|
||||
const existing = await listRailwayServiceDomains({
|
||||
token: opts.token,
|
||||
serviceId: opts.serviceId,
|
||||
environmentId: opts.environmentId,
|
||||
projectId: opts.projectId,
|
||||
});
|
||||
|
||||
const serviceDomain = existing.serviceDomains.find((item) => item.domain);
|
||||
if (serviceDomain) {
|
||||
return serviceDomain;
|
||||
}
|
||||
|
||||
const issuedCustomDomain = existing.customDomains.find((item) =>
|
||||
item.domain && (!item.certificateStatus || item.certificateStatus === "ISSUED")
|
||||
);
|
||||
if (issuedCustomDomain) {
|
||||
return issuedCustomDomain;
|
||||
}
|
||||
|
||||
return await createRailwayServiceDomain({
|
||||
token: opts.token,
|
||||
serviceId: opts.serviceId,
|
||||
environmentId: opts.environmentId,
|
||||
targetPort: opts.targetPort,
|
||||
});
|
||||
}
|
||||
|
||||
/** Apaga o webhook do Telegram para que o Hermes assuma via polling. */
|
||||
export async function deleteTelegramWebhook(botToken: string): Promise<void> {
|
||||
const url = `https://api.telegram.org/bot${botToken}/deleteWebhook?drop_pending_updates=false`;
|
||||
|
|
|
|||
1035
supabase/functions/_shared/runtime-sync.ts
Normal file
1035
supabase/functions/_shared/runtime-sync.ts
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue