Skip to content
43 linesCodeBlameRaw
1/**
2 * What the prompt says about an agent's abilities outside g1t
3 * (abilities.ts), and what a turn "said" for "alone when asked for it".
4 * Pure, with type-only imports, so it is tested under Node as it is.
5 */
6import type { Ability, AbilitySection } from "@g1t/contracts";
7
8/**
9 * The abilities as the prompt tells the agent about them: what each
10 * connected integration lets it do and at which level, its MCP servers,
11 * and what isn't connected, so it asks rather than guesses.
12 */
13export function abilitiesSection(sections: AbilitySection[]): string | null {
14 const lines: string[] = [];
15 const words = (ability: Ability) => (ability.level === "alone" ? "on your own" : ability.level === "asked" ? "on your own only when they asked for it, else it asks first" : ability.level === "ask" ? "asks first (a card)" : "never");
16 for (const section of sections) {
17 if (section.group !== "integration" && section.group !== "mcp") continue;
18 for (const source of section.sources) {
19 if (!source.connected) continue;
20 const ready = source.abilities.filter((ability) => ability.status === "ready");
21 if (!ready.length) continue;
22 lines.push(`- ${source.name}: ${ready.map((ability) => `${ability.label.toLowerCase()} ${words(ability)}`).join("; ")}.`);
23 }
24 }
25 const missing = sections
26 .filter((section) => section.group === "integration")
27 .flatMap((section) => section.sources)
28 .filter((source) => !source.connected && source.abilities.length)
29 .map((source) => source.name);
30 if (!lines.length && !missing.length) return null;
31 return [
32 "## Your abilities outside g1t",
33 "",
34 "These are set on your Abilities tab and enforced in code: a call that isn't allowed is refused with the rule, and one that asks first posts a card. Never work around a refusal.",
35 ...(lines.length ? ["", ...lines] : []),
36 ...(missing.length ? ["", `Not connected to this workspace: ${missing.join(", ")}. When someone needs one, say so and use request_ability.`] : []),
37 ].join("\n");
38}
39
40/** What a turn says, for "alone when asked for it": the latest messages from people, or a session's goal and steering. */
41export function saidText(parts: (string | null | undefined)[]): string {
42 return parts.filter((part): part is string => typeof part === "string" && part.trim().length > 0).join("\n").slice(0, 20_000);
43}