@domesystems/templates 0.4.0 → 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.0",
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
@@ -64,6 +64,30 @@ function quotaBlocks(quotas, agents) {
64
64
  });
65
65
  }
66
66
 
67
+ function managedModelReference(manifest, name) {
68
+ if (manifest.pools.some((pool) => pool.name === name)) return `dome_llm_pool.${label(name)}.name`;
69
+ if (manifest.models.some((model) => model.name === name)) return `dome_llm_connection.${label(name)}.name`;
70
+ throw new Error(`Managed agent default_model ${name} must name a pool or model.`);
71
+ }
72
+
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;
89
+ }
90
+
67
91
  export function renderDomeTerraform(manifest) {
68
92
  if (!manifest.gateways.length) throw new Error("template.yaml must declare at least one gateway.");
69
93
  const blocks = [];
@@ -94,19 +118,11 @@ export function renderDomeTerraform(manifest) {
94
118
  }
95
119
  for (const agent of manifest.agents) {
96
120
  const agentLabel = label(agent.name); const managed = agent.type === "managed"; const resourceType = managed ? "dome_managed_agent" : "dome_agent";
97
- const lines = [attr("workspace_id", "var.workspace_id"), attr("name", q(agent.name)), attr("allowed_gateways", `[${(agent.allowed_gateways ?? []).map((gateway) => `dome_gateway.${label(gateway)}.name`).join(", ")}]`)];
98
- if (!managed && agent.act_as) {
99
- const { method, required: actAsRequired, hmac_secret: hmacSecret, allowed_subjects: allowedSubjects } = agent.act_as;
100
- if (!method) throw new Error(`Agent ${agent.name} act_as needs method.`);
101
- lines.push(attr("act_as", `{ method = ${q(method)}, required = ${Boolean(actAsRequired)} }`));
102
- if (hmacSecret) lines.push(attr("act_as_hmac_secret", expression(hmacSecret)));
103
- if (allowedSubjects) lines.push(attr("actas_allowed_subjects", `[${allowedSubjects.map(q).join(", ")}]`));
104
- }
121
+ const lines = [attr("workspace_id", "var.workspace_id"), attr("name", q(agent.name)), ...agentAttributes(agent)];
105
122
  if (managed) {
106
- if (!agent.system_prompt?.path || !agent.default_model) throw new Error(`Managed agent ${agent.name} needs system_prompt.path and default_model.`);
107
- lines.push(attr("system_prompt", `file("${'${path.module}'}/${agent.system_prompt.path}")`), attr("default_model", `dome_llm_pool.${label(agent.default_model)}.name`));
108
- const trigger = agent.trigger ?? {}; const config = { ...(trigger.manual === undefined ? {} : { manual: { enabled: Boolean(trigger.manual) } }), ...(trigger.webhook === undefined ? {} : { webhook: { enabled: Boolean(trigger.webhook) } }), ...(trigger.schedule ? { schedule: trigger.schedule } : {}) };
109
- if (Object.keys(config).length) lines.push(attr("trigger_config", `jsonencode(${JSON.stringify(config)})`));
123
+ if (!agent.system_prompt?.content || !agent.default_model) throw new Error(`Managed agent ${agent.name} needs system_prompt and default_model.`);
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)})`));
110
126
  }
111
127
  blocks.push(block(resourceType, agentLabel, lines), ...rulesBlock(agent, resourceType, agentLabel));
112
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))]));
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) {
@@ -67,6 +70,16 @@ function normalizeRuleFiles(root, agent, index) {
67
70
  return { ...agent, rules: { ...agent.rules, files: normalized } };
68
71
  }
69
72
 
73
+ function normalizeManagedAgent(root, agent, index) {
74
+ if (agent.type !== "managed") return agent;
75
+ const prompt = agent.system_prompt;
76
+ if (!prompt || typeof prompt !== "object" || Array.isArray(prompt)) throw new Error(`agents[${index}].system_prompt must declare path or inline.`);
77
+ const hasPath = typeof prompt.path === "string";
78
+ const hasInline = typeof prompt.inline === "string";
79
+ if (hasPath === hasInline) throw new Error(`agents[${index}].system_prompt must declare exactly one of path or inline.`);
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" } };
81
+ }
82
+
70
83
  function named(items, label) {
71
84
  const names = new Set();
72
85
  for (const item of items ?? []) {
@@ -84,15 +97,23 @@ export function readManifest(templateDirectory) {
84
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.");
85
98
  if (!Array.isArray(manifest.agents) || manifest.agents.length === 0) throw new Error("template.yaml must declare at least one agent.");
86
99
  const agents = manifest.agents.map((entry, index) => {
87
- const agent = normalizeRuleFiles(root, expandAgent(root, entry, 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);
88
103
  if (typeof agent.name !== "string" || !agent.name.trim()) throw new Error(`agents[${index}].name is required.`);
89
104
  const type = agent.type;
90
105
  if (!type) throw new Error(`agents[${index}].type is required.`);
91
106
  if (!["external", "managed"].includes(type)) throw new Error(`agents[${index}].type must be external or managed.`);
92
107
  if (type === "managed" && agent.keys?.length) throw new Error(`agents[${index}] is managed and cannot declare keys.`);
93
- return { ...agent, type };
108
+ const { __source_directory, ...cleanAgent } = agent;
109
+ return { ...cleanAgent, type };
94
110
  });
95
111
  const filters = normalizeFilterConfigs(root, expandFilters(root, manifest.filters));
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
+ }
96
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");
97
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 };
98
119
  }