| 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 | */ |
| 6 | import 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 | */ |
| 13 | export 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. */ |
| 41 | export 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 | } |