@domesystems/templates 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@domesystems/templates",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Pull, publish, and run versioned Dome templates.",
5
5
  "repository": {
6
6
  "type": "git",
package/src/cli.mjs CHANGED
@@ -61,10 +61,11 @@ function generate(options, output) {
61
61
  output(line("success", `Generated ${file}`, options));
62
62
  }
63
63
  function runtimeFor(directory, manifest, selectedAgent) {
64
- const candidates = selectedAgent ? manifest.agents.filter((agent) => agent.name === selectedAgent) : manifest.agents.filter((agent) => agent.type === "external");
65
- if (candidates.length !== 1) throw new Error(`templates up requires exactly one external agent; found ${candidates.length}. Pass --agent <name> to select one.`);
64
+ const external = manifest.agents.filter((agent) => agent.type === "external");
65
+ const candidates = selectedAgent ? manifest.agents.filter((agent) => agent.name === selectedAgent) : (external.length ? external : manifest.agents);
66
+ if (candidates.length !== 1) throw new Error(`templates up requires exactly one agent to run; found ${candidates.length}. Pass --agent <name> to select one.`);
66
67
  const agent = candidates[0];
67
- if (agent.type !== "external") throw new Error(`templates up cannot start managed agent ${agent.name}; Dome runs its loop.`);
68
+ if (agent.type === "managed") return { ...agent, managed: true };
68
69
  const configPath = path.join("agents", agent.name);
69
70
  const configFile = path.join(directory, configPath, "agent.yaml");
70
71
  if (!fs.existsSync(configFile)) throw new Error(`External agent ${agent.name} needs ${configPath}/agent.yaml.`);
@@ -115,10 +116,10 @@ async function up(options, output) {
115
116
  const target = path.resolve(process.cwd(), options.directory ?? ".");
116
117
  const manifest = readManifest(target);
117
118
  const runtimeAgent = runtimeFor(target, manifest, options.agent);
118
- const needsCredentials = !configured(target);
119
+ const needsCredentials = !runtimeAgent.managed && !configured(target);
119
120
  output(line("info", `Checking ${target}`, options));
120
- fs.writeFileSync(path.join(target, ".dome-compose.yaml"), renderCompose(runtimeAgent.runtime, options.port, { debug: options.debug, configPath: runtimeAgent.configPath }));
121
- if (options.importJob) await configureRuntime(target, options.importJob, runtimeAgent.name, runtimeAgent.localKey, options, output);
121
+ if (!runtimeAgent.managed) fs.writeFileSync(path.join(target, ".dome-compose.yaml"), renderCompose(runtimeAgent.runtime, options.port, { debug: options.debug, configPath: runtimeAgent.configPath }));
122
+ if (options.importJob && !runtimeAgent.managed) await configureRuntime(target, options.importJob, runtimeAgent.name, runtimeAgent.localKey, options, output);
122
123
  else if (options.provision && (needsCredentials || options.forceProvision)) {
123
124
  output(line("info", "Setting up Dome resources", options));
124
125
  let imported;
@@ -126,10 +127,11 @@ async function up(options, output) {
126
127
  catch (caught) { const dashboardURL = importDashboardURL(target); throw new Error(`${caught.message}${dashboardURL ? ` View the import details: ${dashboardURL}` : ""}`); }
127
128
  const job = imported?.data?.job;
128
129
  if (!job?.id) throw new Error("Dome import did not return a job ID.");
129
- if (importCreatedResources(job) && importHasPendingOutputs(job)) await configureRuntime(target, job.id, runtimeAgent.name, runtimeAgent.localKey, options, output);
130
+ if (!runtimeAgent.managed && importCreatedResources(job) && importHasPendingOutputs(job)) await configureRuntime(target, job.id, runtimeAgent.name, runtimeAgent.localKey, options, output);
130
131
  else if (needsCredentials) throw new Error(`Dome matched existing resources and did not create a new runtime token. Recover a pending import with templates up --dir ${options.directory ?? "."} --import-job <job-id>, or rotate the local agent key and set DOME_TOKEN and DOME_GATEWAY_URL in .env.`);
131
132
  }
132
- if (options.start) { const compose = composeCommands(options); run("docker", compose.pull, target); run("docker", compose.up, target); }
133
+ if (options.start && !runtimeAgent.managed) { const compose = composeCommands(options); run("docker", compose.pull, target); run("docker", compose.up, target); }
134
+ if (runtimeAgent.managed) output(line("success", `Managed agent ${runtimeAgent.name} is provisioned in Dome; Dome owns its loop and triggers.`, options));
133
135
  }
134
136
  async function publish(options, output, fetchImplementation) { const target = path.resolve(process.cwd(), options.directory ?? "."); const manifest = readPublishManifest(target); const source = publishSource(target); const name = manifest.metadata.name; const [scope, slug] = name.split("/"); const archive = options.dryRun ? null : createArchive(source); output(line("info", `Checking ${name}@${manifest.metadata.version}`, options)); const payload = { name, version: manifest.metadata.version, title: manifest.metadata.title, summary: manifest.metadata.summary ?? "", sha256: archive?.sha256, size_bytes: archive?.buffer.length, source: { repository: manifest.metadata.source.repository, path: manifest.metadata.source.path, commit: source.commit, remote: source.remote, files: source.files } };
135
137
  const remote = remoteTarget(options); let registry; let attempt;
package/src/generate.mjs CHANGED
@@ -70,21 +70,22 @@ function managedModelReference(manifest, name) {
70
70
  throw new Error(`Managed agent default_model ${name} must name a pool or model.`);
71
71
  }
72
72
 
73
- function triggerBlocks(triggers) {
74
- return triggers.map((trigger) => {
75
- const kind = required(trigger, "kind", `Trigger ${trigger.name} needs kind.`);
76
- if (!["schedule", "webhook"].includes(kind)) throw new Error(`Trigger ${trigger.name} has unsupported kind ${kind}.`);
77
- if (kind === "schedule" && !trigger.cron) throw new Error(`Schedule trigger ${trigger.name} needs cron.`);
78
- const lines = [attr("workspace_id", "var.workspace_id"), attr("name", q(required(trigger, "name", "Every trigger needs a name."))), ...(trigger.description ? [attr("description", q(trigger.description))] : []), attr("kind", q(kind))];
79
- for (const key of ["enabled", "cron", "timezone", "input", "max_concurrent_runs", "catch_up", "catch_up_deadline_seconds", "max_retries"]) if (trigger[key] !== undefined) lines.push(attr(key, typeof trigger[key] === "string" ? q(trigger[key]) : String(trigger[key])));
80
- const targets = trigger.targets ?? [];
81
- if (!Array.isArray(targets) || !targets.length) throw new Error(`Trigger ${trigger.name} needs at least one target.`);
82
- lines.push(`targets = [${targets.map((target) => {
83
- if (target?.kind !== "managed_agent" || typeof target.name !== "string") throw new Error(`Trigger ${trigger.name} targets must name a managed_agent.`);
84
- return `{ kind = "managed_agent", name = dome_managed_agent.${label(target.name)}.name${target.disabled === undefined ? "" : `, disabled = ${Boolean(target.disabled)}`} }`;
85
- }).join(", ")}]`);
86
- return block("dome_trigger", label(trigger.name), lines);
87
- });
73
+ function agentAttributes(agent) {
74
+ const lines = [];
75
+ if (agent.parent) lines.push(attr("parent", `dome_agent.${label(agent.parent)}.name`));
76
+ if (agent.metadata) lines.push(attr("metadata", `{ ${Object.entries(agent.metadata).map(([key, value]) => `${q(key)} = ${q(value)}`).join(", ")} }`));
77
+ for (const [key, resource] of [["allowed_pools", "dome_llm_pool"], ["allowed_models", "dome_llm_connection"], ["allowed_gateways", "dome_gateway"]]) {
78
+ if (agent[key] !== undefined) lines.push(attr(key, `[${agent[key].map((name) => `${resource}.${label(name)}.name`).join(", ")}]`));
79
+ }
80
+ if (agent.allowed_tools !== undefined) lines.push(attr("allowed_tools", `[${agent.allowed_tools.map(q).join(", ")}]`));
81
+ for (const key of ["suspended", "token_ttl", "interactive_access", "secrets_version"]) if (agent[key] !== undefined) lines.push(attr(key, typeof agent[key] === "string" ? q(agent[key]) : String(agent[key])));
82
+ if (agent.act_as) {
83
+ const values = Object.entries(agent.act_as).filter(([key]) => !["hmac_secret"].includes(key)).map(([key, value]) => `${key} = ${typeof value === "string" ? q(value) : String(value)}`);
84
+ lines.push(attr("act_as", `{ ${values.join(", ")} }`));
85
+ if (agent.act_as.hmac_secret) lines.push(attr("act_as_hmac_secret", expression(agent.act_as.hmac_secret)));
86
+ }
87
+ for (const key of ["actas_allowed_groups", "actas_allowed_emails", "actas_allowed_subjects"]) if (agent[key] !== undefined) lines.push(attr(key, `[${agent[key].map(q).join(", ")}]`));
88
+ return lines;
88
89
  }
89
90
 
90
91
  export function renderDomeTerraform(manifest) {
@@ -117,23 +118,16 @@ export function renderDomeTerraform(manifest) {
117
118
  }
118
119
  for (const agent of manifest.agents) {
119
120
  const agentLabel = label(agent.name); const managed = agent.type === "managed"; const resourceType = managed ? "dome_managed_agent" : "dome_agent";
120
- const lines = [attr("workspace_id", "var.workspace_id"), attr("name", q(agent.name))];
121
- if (!managed) lines.push(attr("allowed_gateways", `[${(agent.allowed_gateways ?? []).map((gateway) => `dome_gateway.${label(gateway)}.name`).join(", ")}]`));
122
- if (!managed && agent.act_as) {
123
- const { method, required: actAsRequired, hmac_secret: hmacSecret, allowed_subjects: allowedSubjects } = agent.act_as;
124
- if (!method) throw new Error(`Agent ${agent.name} act_as needs method.`);
125
- lines.push(attr("act_as", `{ method = ${q(method)}, required = ${Boolean(actAsRequired)} }`));
126
- if (hmacSecret) lines.push(attr("act_as_hmac_secret", expression(hmacSecret)));
127
- if (allowedSubjects) lines.push(attr("actas_allowed_subjects", `[${allowedSubjects.map(q).join(", ")}]`));
128
- }
121
+ const lines = [attr("workspace_id", "var.workspace_id"), attr("name", q(agent.name)), ...agentAttributes(agent)];
129
122
  if (managed) {
130
123
  if (!agent.system_prompt?.content || !agent.default_model) throw new Error(`Managed agent ${agent.name} needs system_prompt and default_model.`);
131
- lines.push(attr("system_prompt", "<<-PROMPT"), ...agent.system_prompt.content.split("\n").map((line) => line.trimEnd()), "PROMPT", attr("default_model", managedModelReference(manifest, agent.default_model)), attr("gateway", `dome_gateway.${label(agent.gateway)}.name`));
124
+ lines.push(attr("system_prompt", "<<-PROMPT"), ...agent.system_prompt.content.split("\n").map((line) => line.trimEnd()), "PROMPT", attr("default_model", managedModelReference(manifest, agent.default_model)));
125
+ if (agent.trigger_config !== undefined) lines.push(attr("trigger_config", `jsonencode(${JSON.stringify(agent.trigger_config)})`));
132
126
  }
133
127
  blocks.push(block(resourceType, agentLabel, lines), ...rulesBlock(agent, resourceType, agentLabel));
134
128
  if (!managed) for (const key of agent.keys ?? []) blocks.push(block("dome_agent_key", `${agentLabel}_${label(required(key, "name", `Agent ${agent.name} keys need a name.`))}`, [attr("workspace_id", "var.workspace_id"), attr("agent", `dome_agent.${agentLabel}.name`), attr("name", q(key.name))]));
135
129
  }
136
- blocks.push(...filterBlocks(manifest.filters), ...quotaBlocks(manifest.quotas, new Map(manifest.agents.map((agent) => [agent.name, agent.type]))), ...triggerBlocks(manifest.triggers));
130
+ blocks.push(...filterBlocks(manifest.filters), ...quotaBlocks(manifest.quotas, new Map(manifest.agents.map((agent) => [agent.name, agent.type]))));
137
131
  const header = `# Generated by @domesystems/templates from template.yaml. Do not edit.\nterraform {\n required_providers {\n dome = { source = ${q("dome-systems/dome")}, version = ${q(">= 0.1.0")} }\n }\n}\n\nvariable "workspace_id" { type = string }`;
138
132
  const outputs = manifest.agents.filter((agent) => agent.type === "external").flatMap((agent) => (agent.keys ?? []).map((key) => {
139
133
  const agentLabel = label(agent.name); const keyLabel = label(key.name);
package/src/manifest.mjs CHANGED
@@ -19,10 +19,13 @@ function sourcePath(root, reference, label) {
19
19
  function expandAgent(root, entry, index) {
20
20
  if (!entry || typeof entry !== "object") throw new Error(`agents[${index}] must be an object or path.`);
21
21
  if (entry.path === undefined) return entry;
22
- if (Object.keys(entry).length !== 1) throw new Error(`agents[${index}] cannot combine path with other fields.`);
23
- const agent = parseYAML(sourcePath(root, entry.path, `agents[${index}]`), `agent fragment ${entry.path}`);
22
+ if (Object.keys(entry).some((key) => key !== "name" && key !== "path")) throw new Error(`agents[${index}] may contain only name and path when path-backed.`);
23
+ if (typeof entry.name !== "string" || !entry.name.trim()) throw new Error(`agents[${index}].name is required when path-backed.`);
24
+ const file = sourcePath(root, entry.path, `agents[${index}]`);
25
+ const agent = parseYAML(file, `agent fragment ${entry.path}`);
24
26
  if (!agent || typeof agent !== "object" || Array.isArray(agent)) throw new Error(`Agent fragment ${entry.path} must be an object.`);
25
- return agent;
27
+ if (agent.name !== undefined) throw new Error(`Agent fragment ${entry.path} must not declare name; template.yaml owns the agent identity.`);
28
+ return { ...agent, name: entry.name, __source_directory: path.dirname(file) };
26
29
  }
27
30
 
28
31
  function expandFilters(root, filters) {
@@ -74,8 +77,6 @@ function normalizeManagedAgent(root, agent, index) {
74
77
  const hasPath = typeof prompt.path === "string";
75
78
  const hasInline = typeof prompt.inline === "string";
76
79
  if (hasPath === hasInline) throw new Error(`agents[${index}].system_prompt must declare exactly one of path or inline.`);
77
- if (typeof agent.gateway !== "string" || !agent.gateway.trim()) throw new Error(`Managed agent ${agent.name} needs gateway.`);
78
- if (agent.allowed_gateways !== undefined || agent.trigger !== undefined) throw new Error(`Managed agent ${agent.name} uses gateway and top-level triggers; allowed_gateways and trigger are not supported.`);
79
80
  return { ...agent, system_prompt: { content: hasPath ? fs.readFileSync(sourcePath(root, prompt.path, `agents[${index}].system_prompt`), "utf8") : prompt.inline, source: hasPath ? prompt.path : "inline" } };
80
81
  }
81
82
 
@@ -96,18 +97,25 @@ export function readManifest(templateDirectory) {
96
97
  if (manifest.apiVersion !== "templates.domesystems.ai/v1" || manifest.kind !== "Template") throw new Error("template.yaml must declare apiVersion templates.domesystems.ai/v1 and kind Template.");
97
98
  if (!Array.isArray(manifest.agents) || manifest.agents.length === 0) throw new Error("template.yaml must declare at least one agent.");
98
99
  const agents = manifest.agents.map((entry, index) => {
99
- const agent = normalizeManagedAgent(root, normalizeRuleFiles(root, expandAgent(root, entry, index), index), index);
100
+ const expanded = expandAgent(root, entry, index);
101
+ const agentRoot = expanded.__source_directory ?? root;
102
+ const agent = normalizeManagedAgent(agentRoot, normalizeRuleFiles(agentRoot, expanded, index), index);
100
103
  if (typeof agent.name !== "string" || !agent.name.trim()) throw new Error(`agents[${index}].name is required.`);
101
104
  const type = agent.type;
102
105
  if (!type) throw new Error(`agents[${index}].type is required.`);
103
106
  if (!["external", "managed"].includes(type)) throw new Error(`agents[${index}].type must be external or managed.`);
104
107
  if (type === "managed" && agent.keys?.length) throw new Error(`agents[${index}] is managed and cannot declare keys.`);
105
- return { ...agent, type };
108
+ const { __source_directory, ...cleanAgent } = agent;
109
+ return { ...cleanAgent, type };
106
110
  });
107
111
  const filters = normalizeFilterConfigs(root, expandFilters(root, manifest.filters));
108
- const triggers = manifest.triggers ?? [];
109
- named(agents, "agents"); named(manifest.gateways ?? [], "gateways"); named(manifest.tool_connections ?? [], "tool_connections"); named(manifest.models ?? [], "models"); named(manifest.pools ?? [], "pools"); named(filters, "filters"); named(triggers, "triggers");
110
- return { ...manifest, variables: manifest.variables ?? {}, gateways: manifest.gateways ?? [], tool_connections: manifest.tool_connections ?? [], models: manifest.models ?? [], pools: manifest.pools ?? [], filters, quotas: manifest.quotas ?? [], triggers, agents };
112
+ if (manifest.triggers !== undefined) throw new Error("triggers is not a top-level template field; declare trigger_config on the managed agent it controls.");
113
+ for (const agent of agents) {
114
+ if (agent.type === "managed" && (!agent.system_prompt || !agent.default_model)) throw new Error(`Managed agent ${agent.name} needs system_prompt and default_model.`);
115
+ if (agent.type === "external" && (agent.system_prompt !== undefined || agent.default_model !== undefined || agent.trigger_config !== undefined)) throw new Error(`External agent ${agent.name} cannot declare managed-agent loop fields.`);
116
+ }
117
+ named(agents, "agents"); named(manifest.gateways ?? [], "gateways"); named(manifest.tool_connections ?? [], "tool_connections"); named(manifest.models ?? [], "models"); named(manifest.pools ?? [], "pools"); named(filters, "filters");
118
+ return { ...manifest, variables: manifest.variables ?? {}, gateways: manifest.gateways ?? [], tool_connections: manifest.tool_connections ?? [], models: manifest.models ?? [], pools: manifest.pools ?? [], filters, quotas: manifest.quotas ?? [], agents };
111
119
  }
112
120
 
113
121
  const PACKAGE_NAME = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?\/[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;