import type { EndpointKind } from "./types.js"; export interface AgentDef { name: string; role?: string; kind?: EndpointKind; description?: string; tags?: string[]; /** The connector/harness this persona pins (e.g. `claude`, `jcode`). Honored with the same * precedence as {@link model}/{@link variant}: an explicit `--agent` flag wins over the file, * the file wins over `COTAL_DEFAULT_AGENT`, which wins over the product default. A value * naming an unregistered connector fails loud at spawn (registry resolve, no fallback). */ agent?: string; /** The *active* read set: channels this agent subscribes to at boot (the live chat-durable * filter; mutable at runtime via join/leave). Must be ⊆ {@link allowSubscribe}. Omitted or * empty ⇒ NO channels: an agent reads exactly the channels it lists, and a file that names * none joins none (it stays reachable by DM, presence and anycast). List `general` to get it. */ subscribe?: string[]; /** The read **ACL**: channels this agent *may* read (auth mode → minted as per-channel * history-consumer create grants; the live durable's filter is also held within it). Entries * may be wildcard subtrees (`team.>`). Omitted ⇒ defaults to {@link subscribe} — it can read * exactly what it subscribes to. */ allowSubscribe?: string[]; /** The post **ACL**: channels this agent may publish to (auth mode → minted into pub-allow * ACLs). Entries may be wildcard subtrees (`team.>`). Omitted ⇒ **deny** (default-deny): * publishing is the dangerous capability, so it must be declared explicitly. */ allowPublish?: string[]; /** Per-channel attention DEFAULT: channels delivered but never waking this agent — per-channel * `dnd`. Concrete channels within the read ACL (`allowSubscribe`). One-way operator default; the runtime toggle is * connector state, never written back here (the file is a shared template). */ quiet?: string[]; /** Per-channel attention DEFAULT: channels dropped on receive (incl. `@`-mentions) — "don't receive * this channel". Same one-way default semantics as {@link quiet}. */ muted?: string[]; /** Model override handed to the agent CLI (e.g. `claude --model`). */ model?: string; /** Connector-defined model variant handed to the launcher (e.g. OpenCode reasoning effort). */ variant?: string; /** Opaque, connector-specific launch options — an arbitrary key→value map that core never * interprets. Connectors forward well-shaped keys raw into their host form (`claude` flags, * OpenCode config); a connector with no option surface fails loud. `--opt k=v` on the CLI, a * manifest `launchOptions:`, or a nested `launchOptions:` block here all feed the same bag. */ launchOptions?: Record; /** Capabilities this agent may exercise on the control plane (auth mode → minted into the * cred's publish allow-list). Today `spawn` is the only one: it grants publish to the * privileged control subject (start/purge/definePersona/named stop). Default-deny when * absent — nats-server, not a handler, is the boundary. Granting authority is operator-level * (`definePersona` is itself privileged), so no peer can self-grant via its own agent file. * NOTE: because launchOptions is a raw passthrough, `spawn` is HOST-LAUNCH AUTHORITY — its holder * can drive the connector's full launch surface on the manager host (Claude `--mcp-config` / * `--add-dir` / permission flags, OpenCode agent-config keys). Grant it as host-launch authority, * not as a narrow "add a teammate" permission. */ capabilities?: string[]; /** Authenticated id of the agent that first defined this persona via `definePersona` (P6). A * POLICY field, not content: the privileged tier may *redefine* an existing file only if its * `owner` equals the caller; everyone else needs the admin tier. Fail-closed — an ownerless * file (legacy / operator-written) is admin-only, and a caller can never claim ownership of an * existing file. Set once at creation (owner = creator), preserved on every later redefine. */ owner?: string; /** Frontmatter keys not modelled above, kept verbatim so a connector can read its own launcher * hints without core knowing about each one. */ meta?: Record; /** Markdown body — the agent's persona / appended system prompt. */ persona?: string; } /** Named error prefix when a persona prompt starts with `---` but is not a closed, valid agent file. */ export declare const PROMPT_FRONTMATTER = "prompt-frontmatter"; /** Load and parse an agent definition file (Markdown + `---` frontmatter). */ export declare function loadAgentFile(path: string): AgentDef; /** Parse an agent-file document already in memory. `path` is only for error text. */ export declare function parseAgentFileSource(src: string, path: string): AgentDef; /** Write an agent definition back to disk in the form {@link loadAgentFile} reads: * the set frontmatter fields followed by the persona body. Round-trips through the * parser; creates parent dirs. The runtime persona-definition path uses this to * persist a peer-defined agent as config. */ export declare function saveAgentFile(path: string, def: AgentDef): void; /** Content a peer may name on `define-persona`. Policy (`capabilities`, `owner`) has no slot. */ export type WirePersonaArgs = { name: string; owner: string; prompt: string; model?: string; role?: string; agent?: string; subscribe?: string[]; allowSubscribe?: string[]; allowPublish?: string[]; }; /** * Split a `cotal_persona` prompt that is already a complete agent file into one frontmatter * block plus body. A prompt that does not start with `---` is prose. A prompt that starts with * `---` but is not a closed, valid block fails with {@link PROMPT_FRONTMATTER} rather than being * wrapped. The tool `name` wins over any `name:` inside the prompt. */ export declare function parseLeadingPromptFrontmatter(prompt: string, fallbackName: string): { body: string; def?: AgentDef; }; /** * Build the AgentDef `definePersona` writes. A leading frontmatter block in `prompt` is parsed * and merged: explicit tool arguments win; grants, role, and agent from the prompt survive; * `capabilities` and `owner` from the prompt never do. The result is always one frontmatter block * once {@link saveAgentFile} writes it. On redefine, prompt grants are not applied (the stored * file's policy is preserved) except where an explicit tool argument names a field. */ export declare function composeWirePersona(args: WirePersonaArgs, existing?: AgentDef): AgentDef; /** Resolve a name-or-path to an agent file. A path (absolute, contains a slash — `/` or, on * Windows, `\` — or ends in `.md`) is used as given; a bare name maps to the directory * convention `/.cotal/agents/.md`. */ export declare function agentFilePath(root: string, nameOrPath: string): string; /** One `.cotal/agents/*.md` catalog entry. A malformed file is an `error` row, not a throw — * a list that crashed on one bad card could not tell the caller which names exist. */ export interface PersonaCatalogEntry { name: string; path: string; def?: AgentDef; error?: string; } /** The workspace persona catalog: every `.cotal/agents/*.md` under `root`, sorted by filename * stem. Filesystem-only — no mesh. Missing directory ⇒ empty catalog, not an error. */ export declare function listPersonaCatalog(root: string): PersonaCatalogEntry[]; /** The one-line description `cotal personas list` prints: `description:` if set, else the * first non-empty persona line. Truncated to 100 characters, matching the CLI. */ export declare function personaCatalogDescription(def: AgentDef): string | undefined; /** Same ownership as `definePersona` redefine: the creator, or admin. Ownerless files * (legacy / operator-written) are admin-only. Used by the mesh catalog read so a peer * cannot pull role/model/description/body of a card it could not rewrite. */ export declare function personaCatalogReadable(owner: string | undefined, caller: string, admin: boolean): boolean; /** First free name in the series `base`, `base_2`, `base_3`, … — the first candidate for which * `taken` returns false. The single source of the spawn auto-numbering scheme, shared by the * manager's funnel (checked against its live + reserved slots) and `cotal spawn` (checked against * the live mesh roster), so a colliding name numbers up identically whichever path spawns it. */ export declare function firstFreeName(base: string, taken: (name: string) => boolean): string; /** THE agent-name rule, in one place, for every door that accepts a model- or operator-supplied * name (the manager's spawn ref, identity, roster entry and persona-define; the CLI's `--name`). * * Two constraints, and they are not the same constraint: * - ALWAYS: the name becomes a `.cotal/agents/.md` path, so it must be a bare token and * never a path — this is the traversal guard, and it holds in every mode. * - USER MODE ONLY: the name is additionally the mesh ACTOR, so it must satisfy the principal * grammar, which is strictly narrower — no `-`. Static/open mode keys the actor on the freshly * minted nkey instead of the name, so the narrow rule does not apply there and an existing * `my-agent` persona keeps spawning across an upgrade. * * The narrow half DELEGATES to {@link assertValidOwnerToken} rather than restating it as a second * regex. A copy of an authority's alphabet drifts silently, and the copy that drifts is the one on * the door nobody is reading — the same reason the persona doors share one ownership predicate. * Returns undefined when the name is acceptable, else the refusal to show the caller. */ export declare function spawnNameError(name: string, opts: { userMode: boolean; }): string | undefined; //# sourceMappingURL=agent-file.d.ts.map