@openagentpack/sdk 0.1.0 → 0.2.0-beta-ddef91c-20260720

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/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  defaultFileUploadPurpose,
6
6
  enrichProviderFileInfo
7
- } from "./chunk-TUW6XLDT.js";
7
+ } from "./chunk-OUHQYIG4.js";
8
8
  import {
9
9
  FILE_SCAN_BACKOFF,
10
10
  SCAN_FILE_TIMEOUT_MS,
@@ -14,7 +14,7 @@ import {
14
14
  classifySkillScan,
15
15
  pollUntil,
16
16
  skillStatusFromString
17
- } from "./chunk-7ECSYAQA.js";
17
+ } from "./chunk-CHGDM6TX.js";
18
18
 
19
19
  // src/internal/errors.ts
20
20
  var UserError = class extends Error {
@@ -34,6 +34,7 @@ var REQUIRED_METHODS_BY_KIND = {
34
34
  vault: ["createVault", "deleteVault"],
35
35
  skill: ["createSkill", "updateSkill", "deleteSkill"],
36
36
  agent: ["createAgent", "updateAgent", "deleteAgent"],
37
+ template: ["createTemplate", "updateTemplate", "archiveTemplate"],
37
38
  memory_store: ["createMemoryStore", "deleteMemoryStore"],
38
39
  deployment: ["createDeployment", "updateDeployment", "deleteDeployment", "runDeployment", "getDeployment"],
39
40
  session: ["createSession", "listSessions", "getSession", "deleteSession", "sendSessionMessage"]
@@ -88,7 +89,8 @@ var PROVIDER_ENV_VARS = {
88
89
  },
89
90
  qoder: {
90
91
  api_key: { env: ["QODER_PAT", "QODER_API_KEY"], required: true },
91
- gateway: { env: ["QODER_GATEWAY"], required: false }
92
+ gateway: { env: ["QODER_GATEWAY"], required: false },
93
+ forward_gateway: { env: ["QODER_FORWARD_GATEWAY"], required: false }
92
94
  },
93
95
  claude: {
94
96
  api_key: { env: ["ANTHROPIC_API_KEY", "CLAUDE_API_KEY"], required: true },
@@ -261,11 +263,11 @@ var BaseApiClient = class {
261
263
  await this.throwIfError(res);
262
264
  return Buffer.from(await res.arrayBuffer());
263
265
  }
264
- async *sse(path) {
266
+ async *sse(path, options) {
265
267
  const controller = new AbortController();
266
268
  const res = await fetch(`${this.baseUrl}${path}`, {
267
269
  method: "GET",
268
- headers: { ...this.headers(), Accept: "text/event-stream" },
270
+ headers: { ...this.headers(), Accept: "text/event-stream", ...options?.headers },
269
271
  signal: controller.signal
270
272
  });
271
273
  await this.throwIfError(res);
@@ -417,6 +419,9 @@ function secretPlaceholder(vaultName, credName) {
417
419
  }
418
420
 
419
421
  // src/internal/providers/shared.ts
422
+ function notArchived(raw) {
423
+ return raw.archived_at === null || raw.archived_at === void 0;
424
+ }
420
425
  async function locateRemote(client, endpoint, name, id, accept) {
421
426
  if (!endpoint) return null;
422
427
  const ok = accept ?? (() => true);
@@ -438,6 +443,7 @@ function buildSessionInfo(res, memoryStoreIds) {
438
443
  id: res.id,
439
444
  agent_id: agent?.id ?? res.agent_id ?? "",
440
445
  environment_id: res.environment_id,
446
+ tunnel_id: res.tunnel_id ?? void 0,
441
447
  status: res.status,
442
448
  title: res.title,
443
449
  vault_ids: extractVaultIds(res),
@@ -1052,7 +1058,7 @@ var ClaudeAdapter = class _ClaudeAdapter {
1052
1058
  file: "/files"
1053
1059
  };
1054
1060
  async findResource(type, name, id) {
1055
- const raw = await locateRemote(this.client, _ClaudeAdapter.ENDPOINT_MAP[type], name, id);
1061
+ const raw = await locateRemote(this.client, _ClaudeAdapter.ENDPOINT_MAP[type], name, id, notArchived);
1056
1062
  return raw ? toRemoteResource(raw) : null;
1057
1063
  }
1058
1064
  async listAgents(filter) {
@@ -1246,6 +1252,7 @@ var ClaudeAdapter = class _ClaudeAdapter {
1246
1252
  };
1247
1253
  }
1248
1254
  async createSession(bindings) {
1255
+ if (bindings.delivery === "forward") throw new UserError("Claude does not support Forward sessions.");
1249
1256
  const body = mapSession(bindings);
1250
1257
  const res = await this.client.post("/sessions", body);
1251
1258
  return toSessionInfo(res);
@@ -1345,10 +1352,11 @@ var CLAUDE_CAPABILITIES = {
1345
1352
  vault: { tier: "native", reason: "vaults API" },
1346
1353
  skill: { tier: "native", reason: "skills API with files[] upload" },
1347
1354
  agent: { tier: "native", reason: "managed agents API" },
1355
+ template: { tier: "unsupported", reason: "no Forward Template equivalent on Claude" },
1348
1356
  memory_store: {
1349
1357
  tier: "unsupported",
1350
- reason: "no memory store primitive on Claude",
1351
- remediation: "use skill knowledge or MCP for context persistence"
1358
+ reason: "Claude exposes Memory Stores, but the OpenAgentPack adapter has not implemented them yet",
1359
+ remediation: "use skill knowledge or MCP until Claude Memory Store support is added to the adapter"
1352
1360
  },
1353
1361
  mcp_server: { tier: "native", reason: "mcp_servers field on agent" },
1354
1362
  multiagent: { tier: "native", reason: "coordinator + roster topology" },
@@ -1390,6 +1398,9 @@ var QoderClient = class extends BaseApiClient {
1390
1398
  this.baseUrl = config.gateway ?? "https://api.qoder.com/api/v1/cloud";
1391
1399
  this.apiKey = config.apiKey;
1392
1400
  }
1401
+ isConflict(status) {
1402
+ return status === 409;
1403
+ }
1393
1404
  headers() {
1394
1405
  return {
1395
1406
  "Content-Type": "application/json",
@@ -1460,14 +1471,15 @@ function normalizeToolNameFromQoder(name) {
1460
1471
  return name.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
1461
1472
  }
1462
1473
  function mapEnvironment2(name, decl, projectName) {
1474
+ const envType = decl.config.type ?? "cloud";
1475
+ const config = { type: envType };
1476
+ if (decl.config.networking) config.networking = decl.config.networking;
1477
+ else if (envType === "cloud") config.networking = { type: "unrestricted" };
1478
+ if (decl.config.packages) config.packages = decl.config.packages;
1463
1479
  return {
1464
1480
  name,
1465
1481
  description: decl.description ?? "",
1466
- config: {
1467
- type: "cloud",
1468
- networking: decl.config.networking ?? { type: "unrestricted" },
1469
- packages: decl.config.packages
1470
- },
1482
+ config,
1471
1483
  metadata: injectMetadata(decl.metadata, projectName, name)
1472
1484
  };
1473
1485
  }
@@ -1600,6 +1612,85 @@ function mapMemoryStore(name, decl) {
1600
1612
  description: decl.description
1601
1613
  };
1602
1614
  }
1615
+ function mapDeployment2(name, decl, refs, projectName, uploadedFiles) {
1616
+ const body = {
1617
+ name,
1618
+ agent: refs.agent_version !== void 0 ? { id: refs.agent_id, type: "agent", version: refs.agent_version } : refs.agent_id,
1619
+ environment_id: refs.environment_id,
1620
+ initial_events: mapDeploymentInitialEvents(decl.initial_events)
1621
+ };
1622
+ if (refs.vault_ids.length) body.vault_ids = refs.vault_ids;
1623
+ const resources = mapDeploymentResources2(decl, refs, uploadedFiles);
1624
+ if (resources.length) body.resources = resources;
1625
+ if (decl.schedule) {
1626
+ body.schedule = {
1627
+ type: "cron",
1628
+ expression: decl.schedule.expression,
1629
+ timezone: decl.schedule.timezone
1630
+ };
1631
+ }
1632
+ if (decl.description) body.description = decl.description;
1633
+ if (projectName) {
1634
+ body.metadata = injectMetadata(decl.metadata, projectName, name);
1635
+ } else if (decl.metadata) {
1636
+ body.metadata = decl.metadata;
1637
+ }
1638
+ return body;
1639
+ }
1640
+ function mapDeploymentInitialEvents(events) {
1641
+ return events.map((ev) => {
1642
+ if (ev.type === "user.message" || ev.type === "system.message") {
1643
+ return { type: ev.type, content: [{ type: "text", text: ev.content }] };
1644
+ }
1645
+ const out = { type: "user.define_outcome" };
1646
+ if (ev.description) out.description = ev.description;
1647
+ if (ev.rubric) {
1648
+ out.rubric = { type: "text", content: ev.rubric };
1649
+ } else if (ev.rubric_file) {
1650
+ out.rubric = { type: "file", file_id: ev.rubric_file };
1651
+ }
1652
+ if (ev.max_iterations !== void 0) out.max_iterations = ev.max_iterations;
1653
+ return out;
1654
+ });
1655
+ }
1656
+ function mapDeploymentResources2(decl, refs, uploadedFiles) {
1657
+ const resources = [];
1658
+ const seenStores = /* @__PURE__ */ new Set();
1659
+ for (const r of decl.resources ?? []) {
1660
+ if (r.type === "file") {
1661
+ const fileId = r.file_id ?? (r.source ? uploadedFiles?.get(r.source) : void 0);
1662
+ if (fileId) {
1663
+ const entry = { type: "file", file_id: fileId };
1664
+ if (r.mount_path) entry.mount_path = r.mount_path;
1665
+ resources.push(entry);
1666
+ }
1667
+ } else if (r.type === "github_repository") {
1668
+ const entry = { type: "github_repository", url: r.url };
1669
+ if (r.authorization_token) entry.authorization_token = r.authorization_token;
1670
+ if (r.checkout?.branch) entry.checkout = { type: "branch", name: r.checkout.branch };
1671
+ else if (r.checkout?.commit) entry.checkout = { type: "commit", sha: r.checkout.commit };
1672
+ if (r.mount_path) entry.mount_path = r.mount_path;
1673
+ resources.push(entry);
1674
+ } else if (r.type === "memory_store") {
1675
+ const id = refs.memory_store_ids[r.memory_store];
1676
+ if (id && !seenStores.has(id)) {
1677
+ seenStores.add(id);
1678
+ const entry = { type: "memory_store", memory_store_id: id };
1679
+ if (r.access) entry.access = r.access;
1680
+ if (r.instructions) entry.instructions = r.instructions;
1681
+ resources.push(entry);
1682
+ }
1683
+ }
1684
+ }
1685
+ for (const m of decl.memory_stores ?? []) {
1686
+ const id = refs.memory_store_ids[m];
1687
+ if (id && !seenStores.has(id)) {
1688
+ seenStores.add(id);
1689
+ resources.push({ type: "memory_store", memory_store_id: id });
1690
+ }
1691
+ }
1692
+ return resources;
1693
+ }
1603
1694
  function mapAgent2(name, decl, refs, version, projectName) {
1604
1695
  let model;
1605
1696
  if (typeof decl.model === "string") {
@@ -1663,6 +1754,72 @@ function mapAgent2(name, decl, refs, version, projectName) {
1663
1754
  }
1664
1755
  return body;
1665
1756
  }
1757
+ function mapForwardTemplate(name, decl, refs, projectName) {
1758
+ let model;
1759
+ if (typeof decl.model === "string") {
1760
+ model = decl.model;
1761
+ } else {
1762
+ const qoderModel = decl.model.qoder;
1763
+ if (!qoderModel) throw new UserError(`No Qoder model specified for template '${name}'`);
1764
+ model = typeof qoderModel === "string" ? qoderModel : qoderModel.id;
1765
+ }
1766
+ const body = {
1767
+ name,
1768
+ description: decl.description ?? "",
1769
+ model,
1770
+ system: decl.instructions,
1771
+ environment_id: refs.environment_id,
1772
+ vault_ids: refs.vault_ids
1773
+ };
1774
+ if (refs.tunnel_id) body.tunnel_id = refs.tunnel_id;
1775
+ if (projectName) body.metadata = injectMetadata(decl.metadata, projectName, name);
1776
+ else body.metadata = decl.metadata ?? {};
1777
+ if (decl.tools) {
1778
+ const permissions = decl.tools.permissions ?? {};
1779
+ body.tools = [
1780
+ {
1781
+ type: "agent_toolset_20260401",
1782
+ configs: decl.tools.builtin.map((tool) => {
1783
+ const normalized = normalizeToolNameForQoder(tool);
1784
+ const policy = permissions[tool] ?? permissions[tool.toLowerCase()] ?? permissions[normalized];
1785
+ return {
1786
+ name: normalized,
1787
+ enabled: true,
1788
+ ...policy ? { permission_policy: { type: policy === "ask" ? "always_ask" : "always_allow" } } : {}
1789
+ };
1790
+ })
1791
+ }
1792
+ ];
1793
+ } else {
1794
+ body.tools = [{ type: "agent_toolset_20260401" }];
1795
+ }
1796
+ body.mcp_servers = (decl.mcp_servers ?? []).map((server) => {
1797
+ if (server.type === "official" || !server.url) {
1798
+ throw new UserError(`Qoder MCP server '${server.name}' requires a url`);
1799
+ }
1800
+ return { name: server.name, type: "http", url: server.url };
1801
+ });
1802
+ if (decl.mcp_servers?.length) {
1803
+ const tools = body.tools;
1804
+ for (const server of decl.mcp_servers) {
1805
+ const toolkit = decl.tools?.mcp?.find((item) => item.mcp_server_name === server.name);
1806
+ if (toolkit) {
1807
+ tools.push({
1808
+ type: "mcp_toolset",
1809
+ mcp_server_name: server.name,
1810
+ configs: toolkit.configs
1811
+ });
1812
+ }
1813
+ }
1814
+ }
1815
+ body.skills = refs.skill_ids.map((skill) => ({
1816
+ type: skill.type === "official" ? "qoder" : skill.type,
1817
+ skill_id: skill.skill_id,
1818
+ ...skill.version ? { version: skill.version } : {},
1819
+ enabled: true
1820
+ }));
1821
+ return body;
1822
+ }
1666
1823
  function mapSendMessage2(text) {
1667
1824
  return {
1668
1825
  events: [{ type: "user.message", content: [{ type: "text", text }] }]
@@ -1673,9 +1830,12 @@ var QODER_EVENT_MAP = {
1673
1830
  "user.message": "message",
1674
1831
  "agent.tool_use": "tool_use",
1675
1832
  "agent.tool_result": "tool_result",
1833
+ "agent.mcp_tool_use": "tool_use",
1834
+ "agent.mcp_tool_result": "tool_result",
1676
1835
  "agent.thinking": "thinking",
1677
1836
  "session.status_idle": "status",
1678
1837
  "session.status_running": "status",
1838
+ "session.status_terminated": "status",
1679
1839
  "session.thread_status_idle": "status",
1680
1840
  "session.error": "error"
1681
1841
  };
@@ -1752,6 +1912,7 @@ function mapSession2(bindings) {
1752
1912
  agent: bindings.agent_id,
1753
1913
  environment_id: bindings.environment_id
1754
1914
  };
1915
+ if (bindings.tunnel_id) body.tunnel_id = bindings.tunnel_id;
1755
1916
  if (bindings.title) body.title = bindings.title;
1756
1917
  if (bindings.metadata) body.metadata = bindings.metadata;
1757
1918
  if (bindings.vault_ids.length) body.vault_ids = bindings.vault_ids;
@@ -1762,36 +1923,28 @@ function mapSession2(bindings) {
1762
1923
  if (resources.length) body.resources = resources;
1763
1924
  return body;
1764
1925
  }
1765
- function mapDeploymentToSession(decl, refs, fileIds) {
1766
- const body = {
1767
- agent: refs.agent_id,
1768
- environment_id: refs.environment_id
1769
- };
1770
- if (decl.description) body.title = decl.description;
1771
- if (refs.vault_ids.length) body.vault_ids = refs.vault_ids;
1772
- const resources = Object.values(refs.memory_store_ids).map((id) => ({
1773
- type: "memory_store",
1774
- memory_store_id: id
1775
- }));
1776
- const fileResources = (decl.resources ?? []).filter((r) => r.type === "file");
1777
- fileIds.forEach((id, index) => {
1778
- const entry = { type: "file", file_id: id };
1779
- const mountPath = fileResources[index]?.mount_path;
1780
- if (mountPath) entry.mount_path = mountPath;
1781
- resources.push(entry);
1782
- });
1783
- if (resources.length) body.resources = resources;
1784
- return body;
1785
- }
1786
1926
 
1787
1927
  // src/internal/providers/qoder/adapter.ts
1928
+ function deriveForwardGateway(cloudGateway) {
1929
+ if (!cloudGateway) return "https://api.qoder.com/api/v1/forward";
1930
+ const trimmed = cloudGateway.replace(/\/$/, "");
1931
+ return trimmed.endsWith("/cloud") ? `${trimmed.slice(0, -"/cloud".length)}/forward` : `${trimmed}/forward`;
1932
+ }
1933
+ var QODER_DEFAULT_IDENTITY_EXTERNAL_ID = "__qca_admin_identity__";
1788
1934
  var QoderAdapter = class _QoderAdapter {
1789
1935
  name = "qoder";
1790
1936
  eventResume = true;
1791
1937
  client;
1938
+ forwardClient;
1792
1939
  projectName;
1793
- constructor(apiKey, gateway, projectName) {
1940
+ forwardSessionIds = /* @__PURE__ */ new Set();
1941
+ defaultForwardIdentityId;
1942
+ constructor(apiKey, gateway, projectName, forwardGateway) {
1794
1943
  this.client = new QoderClient({ apiKey, gateway });
1944
+ this.forwardClient = new QoderClient({
1945
+ apiKey,
1946
+ gateway: forwardGateway ?? deriveForwardGateway(gateway)
1947
+ });
1795
1948
  this.projectName = projectName ?? "";
1796
1949
  }
1797
1950
  async validate() {
@@ -1807,7 +1960,11 @@ var QoderAdapter = class _QoderAdapter {
1807
1960
  // deployment omitted: emulated on Qoder, no remote listing endpoint
1808
1961
  };
1809
1962
  async findResource(type, name, id) {
1810
- const raw = await locateRemote(this.client, _QoderAdapter.ENDPOINT_MAP[type], name, id);
1963
+ if (type === "template") {
1964
+ const raw2 = await locateRemote(this.forwardClient, "/templates", name, id, (item) => item.status !== "archived");
1965
+ return raw2 ? toRemoteResource(raw2) : null;
1966
+ }
1967
+ const raw = await locateRemote(this.client, _QoderAdapter.ENDPOINT_MAP[type], name, id, notArchived);
1811
1968
  return raw ? toRemoteResource(raw) : null;
1812
1969
  }
1813
1970
  async listAgents(filter) {
@@ -1852,14 +2009,21 @@ var QoderAdapter = class _QoderAdapter {
1852
2009
  return toRestSkillInfo(res);
1853
2010
  }
1854
2011
  getDriftSupport(type) {
1855
- if (type === "agent" || type === "environment") return "full";
2012
+ if (type === "agent" || type === "environment" || type === "template") return "full";
1856
2013
  if (type === "deployment") return "unsupported";
1857
2014
  return _QoderAdapter.ENDPOINT_MAP[type] ? "existence" : "unsupported";
1858
2015
  }
1859
2016
  async readComparableResource(type, id, name) {
1860
- if (type !== "agent" && type !== "environment") return null;
1861
- const endpoint = type === "agent" ? "/agents" : "/environments";
1862
- const raw = await locateRemote(this.client, endpoint, name, id);
2017
+ if (type !== "agent" && type !== "environment" && type !== "template") return null;
2018
+ const isTemplate = type === "template";
2019
+ const endpoint = type === "agent" ? "/agents" : type === "environment" ? "/environments" : "/templates";
2020
+ const raw = await locateRemote(
2021
+ isTemplate ? this.forwardClient : this.client,
2022
+ endpoint,
2023
+ name,
2024
+ id,
2025
+ isTemplate ? (item) => item.status !== "archived" : notArchived
2026
+ );
1863
2027
  if (!raw) return null;
1864
2028
  const comparable = this.normalizeRemote(type, raw);
1865
2029
  return {
@@ -1883,6 +2047,7 @@ var QoderAdapter = class _QoderAdapter {
1883
2047
  mapAgent2(name, decl, { skill_ids: [] }, void 0, this.projectName)
1884
2048
  );
1885
2049
  }
2050
+ if (type === "template") return null;
1886
2051
  return null;
1887
2052
  }
1888
2053
  normalizeRemote(type, raw) {
@@ -1898,6 +2063,24 @@ var QoderAdapter = class _QoderAdapter {
1898
2063
  metadata: stripAgentsMetadata(raw.metadata)
1899
2064
  });
1900
2065
  }
2066
+ if (type === "template") {
2067
+ return compactDeep({
2068
+ name: raw.name,
2069
+ description: raw.description,
2070
+ model: raw.model,
2071
+ system: raw.system,
2072
+ tools: raw.tools,
2073
+ mcp_servers: raw.mcp_servers,
2074
+ skills: raw.skills,
2075
+ multiagent: raw.multiagent,
2076
+ environment_id: raw.environment_id,
2077
+ tunnel_id: raw.tunnel_id,
2078
+ vault_ids: Array.isArray(raw.vault_ids) ? raw.vault_ids : Object.keys(raw.vaults ?? {}),
2079
+ files: raw.files,
2080
+ environment_variables: raw.environment_variables,
2081
+ metadata: stripAgentsMetadata(raw.metadata)
2082
+ });
2083
+ }
1901
2084
  return compactDeep({
1902
2085
  description: raw.description,
1903
2086
  model: normalizeModel(raw.model),
@@ -1991,6 +2174,30 @@ var QoderAdapter = class _QoderAdapter {
1991
2174
  async deleteAgent(id) {
1992
2175
  await this.client.delete(`/agents/${id}`);
1993
2176
  }
2177
+ async createTemplate(name, decl, refs) {
2178
+ await this.registerForwardVaults(refs.vault_ids);
2179
+ const body = mapForwardTemplate(name, decl, refs, this.projectName);
2180
+ const res = await this.forwardClient.post("/templates", body);
2181
+ return toRemoteResource(res);
2182
+ }
2183
+ async updateTemplate(id, name, decl, refs) {
2184
+ await this.registerForwardVaults(refs.vault_ids);
2185
+ const body = mapForwardTemplate(name, decl, refs, this.projectName);
2186
+ if (!refs.tunnel_id) body.tunnel_id = null;
2187
+ const res = await this.forwardClient.post(`/templates/${id}`, body);
2188
+ return toRemoteResource(res);
2189
+ }
2190
+ async archiveTemplate(id) {
2191
+ await this.forwardClient.post(`/templates/${id}/archive`, {});
2192
+ }
2193
+ async registerForwardVaults(vaultIds) {
2194
+ for (const id of vaultIds) {
2195
+ await this.forwardClient.post("/resources/registry", {
2196
+ type: "vault",
2197
+ resource: { id }
2198
+ });
2199
+ }
2200
+ }
1994
2201
  async createMemoryStore(name, decl) {
1995
2202
  const body = mapMemoryStore(name, decl);
1996
2203
  const res = await this.client.post("/memory_stores", body);
@@ -2008,46 +2215,58 @@ var QoderAdapter = class _QoderAdapter {
2008
2215
  async deleteMemoryStore(id) {
2009
2216
  await this.client.delete(`/memory_stores/${id}`);
2010
2217
  }
2011
- async createDeployment(_name, _decl, _refs, _basePath) {
2012
- return { id: null, type: "deployment" };
2218
+ async createDeployment(name, decl, refs, basePath) {
2219
+ const uploaded = await this.uploadDeploymentFiles(decl, basePath);
2220
+ const body = mapDeployment2(name, decl, refs, this.projectName, uploaded);
2221
+ const res = await this.client.post("/deployments", body);
2222
+ return toRemoteResource(res);
2013
2223
  }
2014
- async updateDeployment(_id, _name, _decl, _refs, _basePath) {
2015
- return { id: null, type: "deployment" };
2224
+ async updateDeployment(id, name, decl, refs, basePath) {
2225
+ const uploaded = await this.uploadDeploymentFiles(decl, basePath);
2226
+ const body = mapDeployment2(name, decl, refs, this.projectName, uploaded);
2227
+ const res = await this.client.post(`/deployments/${id}`, body);
2228
+ return toRemoteResource(res);
2016
2229
  }
2017
- async deleteDeployment(_id) {
2230
+ async deleteDeployment(id) {
2231
+ await this.client.post(`/deployments/${id}/archive`, {});
2018
2232
  }
2019
2233
  async runDeployment(ctx) {
2020
- const fileIds = [];
2021
- for (const r of ctx.decl.resources ?? []) {
2022
- if (r.type === "file") {
2023
- if (r.file_id) {
2024
- fileIds.push(r.file_id);
2025
- } else if (r.source) {
2026
- fileIds.push(await this.uploadSessionFile(r.source, ctx.basePath));
2027
- }
2028
- }
2029
- }
2030
- const body = mapDeploymentToSession(ctx.decl, ctx.refs, fileIds);
2031
- const sessionRes = await this.client.post("/sessions", body);
2032
- const sessionId = sessionRes.id;
2033
- const events = ctx.decl.initial_events.filter((e) => e.type === "user.message" || e.type === "system.message").map((e) => ({
2034
- type: "user.message",
2035
- content: [{ type: "text", text: e.content }]
2036
- }));
2037
- if (events.length) {
2038
- await this.client.post(`/sessions/${sessionId}/events`, { events });
2234
+ if (!ctx.id) {
2235
+ throw new UserError(`Deployment '${ctx.name}' has no remote id; run \`agents apply\` first.`);
2039
2236
  }
2040
- return { session_id: sessionId };
2237
+ const res = await this.client.post(`/deployments/${ctx.id}/run`, {});
2238
+ return {
2239
+ run_id: res.id,
2240
+ session_id: res.session_id ?? null,
2241
+ error: res.error ?? void 0
2242
+ };
2041
2243
  }
2042
2244
  async getDeployment(ctx) {
2043
- const plan = mapDeploymentToSession(ctx.decl, ctx.refs, []);
2245
+ if (!ctx.id) {
2246
+ throw new UserError(`Deployment '${ctx.name}' has no remote id; run \`agents apply\` first.`);
2247
+ }
2248
+ const res = await this.client.get(`/deployments/${ctx.id}`);
2249
+ const sched = res.schedule;
2044
2250
  return {
2045
- id: ctx.id,
2046
- status: "emulated (local)",
2047
- schedule: ctx.decl.schedule,
2048
- attributes: { materialization_plan: plan }
2251
+ id: res.id,
2252
+ status: res.status ?? "unknown",
2253
+ paused_reason: res.paused_reason,
2254
+ schedule: sched ? {
2255
+ expression: sched.expression,
2256
+ timezone: sched.timezone
2257
+ } : void 0,
2258
+ attributes: res
2049
2259
  };
2050
2260
  }
2261
+ async uploadDeploymentFiles(decl, basePath) {
2262
+ const map = /* @__PURE__ */ new Map();
2263
+ for (const r of decl.resources ?? []) {
2264
+ if (r.type === "file" && !r.file_id && r.source && !map.has(r.source)) {
2265
+ map.set(r.source, await this.uploadSessionFile(r.source, basePath));
2266
+ }
2267
+ }
2268
+ return map;
2269
+ }
2051
2270
  async uploadSessionFile(source, basePath) {
2052
2271
  const fullPath = resolve2(dirname2(basePath), source);
2053
2272
  const content = readFileSync2(fullPath);
@@ -2058,11 +2277,73 @@ var QoderAdapter = class _QoderAdapter {
2058
2277
  return res.file_id ?? res.id;
2059
2278
  }
2060
2279
  async createSession(bindings) {
2280
+ if (bindings.delivery === "forward") {
2281
+ const identityId = bindings.identity_id ?? await this.resolveDefaultForwardIdentityId();
2282
+ const body2 = {
2283
+ identity_id: identityId,
2284
+ template_id: bindings.template_id,
2285
+ incremental_streaming_enabled: false
2286
+ };
2287
+ if (bindings.title) body2.title = bindings.title;
2288
+ if (bindings.metadata) body2.metadata = bindings.metadata;
2289
+ if (bindings.files?.length) {
2290
+ body2.resources = bindings.files.map((file) => ({
2291
+ type: "file",
2292
+ file_id: file.file_id,
2293
+ mount_path: file.mount_path
2294
+ }));
2295
+ }
2296
+ const res2 = await this.forwardClient.post("/sessions", body2);
2297
+ const info = toForwardSessionInfo(res2, bindings);
2298
+ this.forwardSessionIds.add(info.id);
2299
+ return info;
2300
+ }
2061
2301
  const body = mapSession2(bindings);
2062
2302
  const res = await this.client.post("/sessions", body);
2063
2303
  return toSessionInfo2(res);
2064
2304
  }
2305
+ async resolveDefaultForwardIdentityId() {
2306
+ if (this.defaultForwardIdentityId) return this.defaultForwardIdentityId;
2307
+ let afterId;
2308
+ do {
2309
+ const params = new URLSearchParams({ limit: "100" });
2310
+ if (afterId) params.set("after_id", afterId);
2311
+ const res = await this.forwardClient.get(`/identities?${params}`);
2312
+ const identities = res.data ?? [];
2313
+ const match = identities.find(
2314
+ (identity) => identity.external_id === QODER_DEFAULT_IDENTITY_EXTERNAL_ID && identity.enabled !== false && identity.archived !== true
2315
+ );
2316
+ if (typeof match?.id === "string") {
2317
+ this.defaultForwardIdentityId = match.id;
2318
+ return match.id;
2319
+ }
2320
+ const hasMore = res.has_more ?? false;
2321
+ const nextId = hasMore ? res.last_id ?? void 0 : void 0;
2322
+ if (!nextId || nextId === afterId) break;
2323
+ afterId = nextId;
2324
+ } while (afterId);
2325
+ throw new UserError(
2326
+ `Qoder default Forward Identity '${QODER_DEFAULT_IDENTITY_EXTERNAL_ID}' was not found. Ask Qoder to provision it, set defaults.session.qoder.identity_id, or pass --identity-id.`
2327
+ );
2328
+ }
2065
2329
  async listSessions(filter) {
2330
+ if (filter?.agent_id?.startsWith("tmpl_")) {
2331
+ const params2 = new URLSearchParams({ template_id: filter.agent_id });
2332
+ if (filter.limit) params2.set("limit", String(filter.limit));
2333
+ if (filter.page) params2.set("after_id", filter.page);
2334
+ const res2 = await this.forwardClient.get(`/sessions?${params2}`);
2335
+ const data2 = res2.data ?? [];
2336
+ const hasMore = res2.has_more ?? false;
2337
+ const nextPage2 = hasMore ? res2.last_id ?? void 0 : void 0;
2338
+ for (const item of data2) {
2339
+ if (typeof item.id === "string") this.forwardSessionIds.add(item.id);
2340
+ }
2341
+ return {
2342
+ sessions: data2.map((item) => toForwardSessionInfo(item)),
2343
+ has_more: hasMore,
2344
+ next_page: nextPage2
2345
+ };
2346
+ }
2066
2347
  const params = new URLSearchParams();
2067
2348
  if (filter?.agent_id) params.set("agent_id", filter.agent_id);
2068
2349
  if (filter?.limit) params.set("limit", String(filter.limit));
@@ -2077,34 +2358,105 @@ var QoderAdapter = class _QoderAdapter {
2077
2358
  };
2078
2359
  }
2079
2360
  async getSession(id) {
2080
- const res = await this.client.get(`/sessions/${id}`);
2081
- return toSessionInfo2(res);
2361
+ if (this.forwardSessionIds.has(id)) return this.getForwardSession(id);
2362
+ try {
2363
+ const res = await this.client.get(`/sessions/${id}`);
2364
+ return toSessionInfo2(res);
2365
+ } catch (error) {
2366
+ if (!ApiError.isNotFound(error)) throw error;
2367
+ return this.getForwardSession(id);
2368
+ }
2082
2369
  }
2083
2370
  async deleteSession(id) {
2084
- await this.client.delete(`/sessions/${id}`);
2371
+ if (this.forwardSessionIds.has(id)) {
2372
+ await this.forwardClient.post(`/sessions/${id}/archive`, {});
2373
+ return;
2374
+ }
2375
+ try {
2376
+ await this.client.delete(`/sessions/${id}`);
2377
+ } catch (error) {
2378
+ if (!ApiError.isNotFound(error)) throw error;
2379
+ await this.forwardClient.post(`/sessions/${id}/archive`, {});
2380
+ this.forwardSessionIds.add(id);
2381
+ }
2085
2382
  }
2086
2383
  async sendSessionMessage(sessionId, message) {
2087
2384
  const body = mapSendMessage2(message);
2088
- const res = await this.client.post(`/sessions/${sessionId}/events`, body);
2089
- return extractCreatedEventId(res);
2385
+ if (this.forwardSessionIds.has(sessionId)) {
2386
+ const res = await this.forwardClient.post(`/sessions/${sessionId}/events`, body);
2387
+ return extractCreatedEventId(res);
2388
+ }
2389
+ try {
2390
+ const res = await this.client.post(`/sessions/${sessionId}/events`, body);
2391
+ return extractCreatedEventId(res);
2392
+ } catch (error) {
2393
+ if (!ApiError.isNotFound(error)) throw error;
2394
+ const res = await this.forwardClient.post(`/sessions/${sessionId}/events`, body);
2395
+ this.forwardSessionIds.add(sessionId);
2396
+ return extractCreatedEventId(res);
2397
+ }
2090
2398
  }
2091
2399
  async *streamSessionEvents(sessionId, options) {
2400
+ if (this.forwardSessionIds.has(sessionId)) {
2401
+ yield* this.streamForwardSessionEvents(sessionId, options);
2402
+ return;
2403
+ }
2092
2404
  const path = `/sessions/${sessionId}/events/stream`;
2093
2405
  let skipping = !!options?.after_id;
2094
2406
  const afterId = options?.after_id;
2095
- for await (const raw of this.client.sse(path)) {
2096
- if (skipping) {
2097
- const eventId = raw.id;
2098
- if (eventId === afterId) {
2099
- skipping = false;
2407
+ try {
2408
+ for await (const raw of this.client.sse(path)) {
2409
+ if (skipping) {
2410
+ const eventId = raw.id;
2411
+ if (eventId === afterId) {
2412
+ skipping = false;
2413
+ }
2414
+ continue;
2100
2415
  }
2101
- continue;
2416
+ yield toSessionEvent3(raw);
2102
2417
  }
2103
- yield toSessionEvent3(raw);
2418
+ } catch (error) {
2419
+ if (!ApiError.isNotFound(error)) throw error;
2420
+ this.forwardSessionIds.add(sessionId);
2421
+ yield* this.streamForwardSessionEvents(sessionId, options);
2104
2422
  }
2105
2423
  }
2106
2424
  async listSessionEvents(sessionId, options) {
2107
- return listSessionEventsPaged(this.client, sessionId, options, toSessionEvent3, { forwardAfterId: true });
2425
+ if (this.forwardSessionIds.has(sessionId)) return this.listForwardSessionEvents(sessionId, options);
2426
+ try {
2427
+ return await listSessionEventsPaged(this.client, sessionId, options, toSessionEvent3, { forwardAfterId: true });
2428
+ } catch (error) {
2429
+ if (!ApiError.isNotFound(error)) throw error;
2430
+ this.forwardSessionIds.add(sessionId);
2431
+ return this.listForwardSessionEvents(sessionId, options);
2432
+ }
2433
+ }
2434
+ async getForwardSession(id) {
2435
+ const res = await this.forwardClient.get(`/sessions/${id}`);
2436
+ this.forwardSessionIds.add(id);
2437
+ return toForwardSessionInfo(res);
2438
+ }
2439
+ async *streamForwardSessionEvents(sessionId, options) {
2440
+ const headers = options?.after_id ? { "Last-Event-ID": options.after_id } : void 0;
2441
+ for await (const raw of this.forwardClient.sse(`/sessions/${sessionId}/events/stream`, { headers })) {
2442
+ yield toSessionEvent3(raw);
2443
+ }
2444
+ }
2445
+ async listForwardSessionEvents(sessionId, options) {
2446
+ const params = new URLSearchParams();
2447
+ if (options?.limit) params.set("limit", String(options.limit));
2448
+ if (options?.order) params.set("order", options.order);
2449
+ const afterId = options?.after_id ?? options?.page_token ?? options?.page;
2450
+ if (afterId) params.set("after_id", afterId);
2451
+ const query = params.toString();
2452
+ const res = await this.forwardClient.get(`/sessions/${sessionId}/events${query ? `?${query}` : ""}`);
2453
+ const data = res.data ?? [];
2454
+ const hasMore = res.has_more ?? false;
2455
+ return {
2456
+ events: data.map(toSessionEvent3),
2457
+ has_more: hasMore,
2458
+ next_page: hasMore ? res.last_id ?? void 0 : void 0
2459
+ };
2108
2460
  }
2109
2461
  async listModels() {
2110
2462
  const res = await this.client.get("/models");
@@ -2138,6 +2490,24 @@ var QoderAdapter = class _QoderAdapter {
2138
2490
  function toSessionInfo2(res) {
2139
2491
  return buildSessionInfo(res, (r) => r.memory_store_ids ?? []);
2140
2492
  }
2493
+ function toForwardSessionInfo(res, bindings) {
2494
+ const template = res.template ?? {};
2495
+ const templateId = res.template_id ?? template.id ?? bindings?.template_id ?? "";
2496
+ const environmentId = res.environment_id ?? template.environment_id ?? "";
2497
+ return {
2498
+ id: res.id,
2499
+ agent_id: templateId,
2500
+ environment_id: environmentId,
2501
+ tunnel_id: res.tunnel_id ?? template.tunnel_id,
2502
+ status: res.status ?? "unknown",
2503
+ title: res.title,
2504
+ vault_ids: res.vault_ids ?? [],
2505
+ memory_store_ids: res.memory_store_ids ?? [],
2506
+ created_at: res.created_at ?? (/* @__PURE__ */ new Date(0)).toISOString(),
2507
+ updated_at: res.updated_at ?? res.created_at ?? (/* @__PURE__ */ new Date(0)).toISOString(),
2508
+ attributes: res
2509
+ };
2510
+ }
2141
2511
  function normalizeModel(value) {
2142
2512
  if (value && typeof value === "object" && "id" in value) {
2143
2513
  return value.id;
@@ -2191,6 +2561,7 @@ var QODER_CAPABILITIES = {
2191
2561
  vault: { tier: "native", reason: "vaults + MCP credentials" },
2192
2562
  skill: { tier: "native", reason: "skills API with zip upload" },
2193
2563
  agent: { tier: "native", reason: "agents API" },
2564
+ template: { tier: "native", reason: "Forward Templates API" },
2194
2565
  memory_store: { tier: "native", reason: "memory_stores API" },
2195
2566
  mcp_server: { tier: "native", reason: "mcp_servers field on agent" },
2196
2567
  multiagent: {
@@ -2199,9 +2570,8 @@ var QODER_CAPABILITIES = {
2199
2570
  remediation: "deploy agents independently and orchestrate via MCP"
2200
2571
  },
2201
2572
  deployment: {
2202
- tier: "emulated",
2203
- reason: "no deployment primitive on Qoder; expanded into a session at run time",
2204
- remediation: "scheduling and outcome rubrics are not enforced server-side \u2014 use external cron/CI for always-on or scheduled runs"
2573
+ tier: "native",
2574
+ reason: "deployments API with scheduled and manual runs"
2205
2575
  },
2206
2576
  session: { tier: "native", reason: "sessions API" }
2207
2577
  };
@@ -2210,7 +2580,8 @@ var QODER_CAPABILITIES = {
2210
2580
  import { z as z2 } from "zod";
2211
2581
  var qoderConfigSchema = z2.object({
2212
2582
  api_key: z2.string(),
2213
- gateway: z2.string().optional()
2583
+ gateway: z2.string().optional(),
2584
+ forward_gateway: z2.string().optional()
2214
2585
  });
2215
2586
 
2216
2587
  // src/internal/providers/qoder/index.ts
@@ -2220,7 +2591,7 @@ registerProvider({
2220
2591
  capabilities: QODER_CAPABILITIES,
2221
2592
  createAdapter: (config, projectName) => {
2222
2593
  const c = config;
2223
- return new QoderAdapter(c.api_key, c.gateway, projectName);
2594
+ return new QoderAdapter(c.api_key, c.gateway, projectName, c.forward_gateway);
2224
2595
  }
2225
2596
  });
2226
2597
 
@@ -2488,7 +2859,7 @@ function mapSession3(bindings) {
2488
2859
  if (bindings.memory_store_ids.length) body.memory_store_ids = bindings.memory_store_ids;
2489
2860
  return body;
2490
2861
  }
2491
- function mapDeploymentToSession2(decl, refs, fileIds) {
2862
+ function mapDeploymentToSession(decl, refs, fileIds) {
2492
2863
  const body = {
2493
2864
  agent: refs.agent_id,
2494
2865
  environment_id: refs.environment_id
@@ -2908,7 +3279,7 @@ var BailianAdapter = class _BailianAdapter {
2908
3279
  }
2909
3280
  }
2910
3281
  }
2911
- const body = mapDeploymentToSession2(ctx.decl, ctx.refs, fileIds);
3282
+ const body = mapDeploymentToSession(ctx.decl, ctx.refs, fileIds);
2912
3283
  const sessionRes = await this.client.post("/sessions", body);
2913
3284
  const sessionId = sessionRes.id;
2914
3285
  const eventsBody = mapInitialEvents2(ctx.decl.initial_events);
@@ -2919,7 +3290,7 @@ var BailianAdapter = class _BailianAdapter {
2919
3290
  return { session_id: sessionId };
2920
3291
  }
2921
3292
  async getDeployment(ctx) {
2922
- const plan = mapDeploymentToSession2(ctx.decl, ctx.refs, []);
3293
+ const plan = mapDeploymentToSession(ctx.decl, ctx.refs, []);
2923
3294
  return {
2924
3295
  id: ctx.id,
2925
3296
  status: "emulated (local)",
@@ -2939,6 +3310,7 @@ var BailianAdapter = class _BailianAdapter {
2939
3310
  }
2940
3311
  // --- Session ---
2941
3312
  async createSession(bindings) {
3313
+ if (bindings.delivery === "forward") throw new UserError("Bailian does not support Forward sessions.");
2942
3314
  const body = mapSession3(bindings);
2943
3315
  const res = await this.client.post("/sessions", body);
2944
3316
  return toSessionInfo3(res);
@@ -3106,6 +3478,7 @@ var BAILIAN_CAPABILITIES = {
3106
3478
  vault: { tier: "native", reason: "vaults + credentials API (static_bearer MCP credentials)" },
3107
3479
  skill: { tier: "native", reason: "skills API with 2-step zip upload via Files API" },
3108
3480
  agent: { tier: "native", reason: "agents API with versioned updates" },
3481
+ template: { tier: "unsupported", reason: "no Forward Template equivalent on Bailian" },
3109
3482
  memory_store: {
3110
3483
  tier: "unsupported",
3111
3484
  reason: "no memory store primitive on Bailian"
@@ -3418,7 +3791,7 @@ function mapAgent4(name, decl, refs, version, projectName) {
3418
3791
  }
3419
3792
  return body;
3420
3793
  }
3421
- function mapDeploymentToSession3(decl, refs, fileIds) {
3794
+ function mapDeploymentToSession2(decl, refs, fileIds) {
3422
3795
  const body = {
3423
3796
  agent: refs.agent_id,
3424
3797
  environment_id: refs.environment_id
@@ -3736,7 +4109,7 @@ var ArkAdapter = class _ArkAdapter {
3736
4109
  }
3737
4110
  }
3738
4111
  }
3739
- const body = mapDeploymentToSession3(ctx.decl, ctx.refs, fileIds);
4112
+ const body = mapDeploymentToSession2(ctx.decl, ctx.refs, fileIds);
3740
4113
  const sessionRes = await this.client.post("/sessions", body);
3741
4114
  const sessionId = sessionRes.id;
3742
4115
  const events = ctx.decl.initial_events.filter((e) => e.type === "user.message" || e.type === "system.message").map((e) => ({
@@ -3749,7 +4122,7 @@ var ArkAdapter = class _ArkAdapter {
3749
4122
  return { session_id: sessionId };
3750
4123
  }
3751
4124
  async getDeployment(ctx) {
3752
- const plan = mapDeploymentToSession3(ctx.decl, ctx.refs, []);
4125
+ const plan = mapDeploymentToSession2(ctx.decl, ctx.refs, []);
3753
4126
  return {
3754
4127
  id: ctx.id,
3755
4128
  status: "emulated (local)",
@@ -3767,6 +4140,7 @@ var ArkAdapter = class _ArkAdapter {
3767
4140
  return res.file_id ?? res.id;
3768
4141
  }
3769
4142
  async createSession(bindings) {
4143
+ if (bindings.delivery === "forward") throw new UserError("Ark does not support Forward sessions.");
3770
4144
  const body = mapSession4(bindings);
3771
4145
  const res = await this.client.post("/sessions", body);
3772
4146
  return toSessionInfo4(res);
@@ -3866,6 +4240,7 @@ var ARK_CAPABILITIES = {
3866
4240
  vault: { tier: "native", reason: "vaults API" },
3867
4241
  skill: { tier: "native", reason: "skills API with single-zip upload (create + get + attach only)" },
3868
4242
  agent: { tier: "native", reason: "managed agents API" },
4243
+ template: { tier: "unsupported", reason: "no Forward Template equivalent on Ark" },
3869
4244
  memory_store: { tier: "native", reason: "memory_stores API" },
3870
4245
  mcp_server: { tier: "native", reason: "mcp_servers field on agent" },
3871
4246
  multiagent: { tier: "native", reason: "coordinator + roster topology" },
@@ -4021,13 +4396,22 @@ var environmentSchema = z5.object({
4021
4396
  name: z5.string().optional(),
4022
4397
  description: z5.string().optional(),
4023
4398
  provider: z5.string().optional(),
4399
+ /** Pre-existing provider environment id (e.g. env_00xxxx). When set, the environment is treated as an external reference and will not be created/updated/deleted by OpenCMA. */
4400
+ environment_id: z5.string().optional(),
4024
4401
  config: z5.object({
4025
- type: z5.literal("cloud"),
4402
+ type: z5.enum(["cloud", "self_hosted"]),
4026
4403
  networking: networkingSchema.optional(),
4027
4404
  packages: packagesSchema.optional()
4028
4405
  }),
4029
4406
  metadata: z5.record(z5.string(), z5.string()).optional()
4030
4407
  });
4408
+ var tunnelSchema = z5.object({
4409
+ name: z5.string().optional(),
4410
+ description: z5.string().optional(),
4411
+ /** Pre-existing Qoder tunnel id (e.g. tnl_00xxxx). Tunnels are allocated by Qoder BYOC admin and referenced, not created. */
4412
+ tunnel_id: z5.string(),
4413
+ metadata: z5.record(z5.string(), z5.string()).optional()
4414
+ });
4031
4415
  var coerceString = z5.union([z5.string(), z5.number()]).transform(String);
4032
4416
  var staticBearerCredentialSchema = z5.object({
4033
4417
  name: z5.string(),
@@ -4136,12 +4520,16 @@ var agentSkillRefSchema = z5.object({
4136
4520
  skill_id: String(s.skill_id ?? s.code),
4137
4521
  version: s.version
4138
4522
  }));
4523
+ var agentDeliverySchema = z5.object({
4524
+ type: z5.enum(["managed", "forward"])
4525
+ });
4139
4526
  var agentSchema = z5.object({
4140
4527
  name: z5.string().optional(),
4141
4528
  description: z5.string().optional(),
4142
4529
  model: z5.union([z5.string(), z5.record(z5.string(), z5.string())]),
4143
4530
  instructions: z5.string(),
4144
4531
  environment: z5.string().optional(),
4532
+ tunnel: z5.string().optional(),
4145
4533
  provider: z5.string().optional(),
4146
4534
  tools: toolsSchema.optional(),
4147
4535
  mcp_servers: z5.array(mcpServerSchema).optional(),
@@ -4149,7 +4537,8 @@ var agentSchema = z5.object({
4149
4537
  vault: z5.string().optional(),
4150
4538
  memory_stores: z5.array(z5.string()).optional(),
4151
4539
  multiagent: multiagentSchema.optional(),
4152
- metadata: z5.record(z5.string(), z5.string()).optional()
4540
+ metadata: z5.record(z5.string(), z5.string()).optional(),
4541
+ delivery: z5.record(z5.string(), agentDeliverySchema).optional()
4153
4542
  });
4154
4543
  var deploymentFileResourceSchema = z5.object({
4155
4544
  type: z5.literal("file"),
@@ -4196,6 +4585,7 @@ var deploymentSchema = z5.object({
4196
4585
  agent: z5.string(),
4197
4586
  agent_version: z5.number().int().optional(),
4198
4587
  environment: z5.string().optional(),
4588
+ tunnel: z5.string().optional(),
4199
4589
  vaults: z5.array(z5.string()).optional(),
4200
4590
  memory_stores: z5.array(z5.string()).optional(),
4201
4591
  resources: z5.array(deploymentResourceSchema).optional(),
@@ -4209,9 +4599,15 @@ var projectConfigSchema = z5.object({
4209
4599
  version: z5.string(),
4210
4600
  providers: z5.record(z5.string(), z5.unknown()),
4211
4601
  defaults: z5.object({
4212
- provider: z5.string().optional()
4602
+ provider: z5.string().optional(),
4603
+ session: z5.object({
4604
+ qoder: z5.object({
4605
+ identity_id: z5.string().min(1).optional()
4606
+ }).optional()
4607
+ }).optional()
4213
4608
  }).optional(),
4214
4609
  environments: z5.record(z5.string(), environmentSchema).optional(),
4610
+ tunnels: z5.record(z5.string(), tunnelSchema).optional(),
4215
4611
  vaults: z5.record(z5.string(), vaultSchema).optional(),
4216
4612
  memory_stores: z5.record(z5.string(), memoryStoreSchema).optional(),
4217
4613
  skills: z5.record(z5.string(), skillSchema).optional(),
@@ -4328,6 +4724,7 @@ function getResourceDeclaration(address, config) {
4328
4724
  case "skill":
4329
4725
  return config.skills?.[name] ?? null;
4330
4726
  case "agent":
4727
+ case "template":
4331
4728
  return config.agents?.[name] ?? null;
4332
4729
  case "file":
4333
4730
  return config.files?.[name] ?? null;
@@ -4369,7 +4766,7 @@ function collectFiles(dir, base) {
4369
4766
  }
4370
4767
 
4371
4768
  // src/internal/planner/hasher.ts
4372
- async function computeResourceHash(address, config, basePath) {
4769
+ async function computeResourceHash(address, config, basePath, state) {
4373
4770
  const decl = getDeclaration(address, config);
4374
4771
  if (!decl) return "";
4375
4772
  if (address.type === "skill") {
@@ -4379,8 +4776,42 @@ async function computeResourceHash(address, config, basePath) {
4379
4776
  return contentHash({ decl, fileHash });
4380
4777
  }
4381
4778
  }
4779
+ if (address.type === "deployment") {
4780
+ const refs = resolveDeploymentReferenceIds(decl, config, address.provider, state);
4781
+ if (refs) return contentHash({ decl, refs });
4782
+ }
4783
+ if (address.type === "template") {
4784
+ const refs = resolveTemplateReferenceIds(decl, config, address.provider, state);
4785
+ return contentHash({ decl, refs });
4786
+ }
4382
4787
  return contentHash(decl);
4383
4788
  }
4789
+ function resolveTemplateReferenceIds(decl, config, provider, state) {
4790
+ const environment = decl.environment ? config.environments?.[decl.environment] : void 0;
4791
+ const tunnel = decl.tunnel ? config.tunnels?.[decl.tunnel] : void 0;
4792
+ const skillIds = (decl.skills ?? []).map((skill) => {
4793
+ if (typeof skill === "string") {
4794
+ return state?.getResource({ type: "skill", name: skill, provider })?.remote_id ?? skill;
4795
+ }
4796
+ if (skill.type === "official") return `${skill.type}:${skill.skill_id}:${skill.version ?? ""}`;
4797
+ return state?.getResource({ type: "skill", name: skill.skill_id, provider })?.remote_id ?? `${skill.type}:${skill.skill_id}:${skill.version ?? ""}`;
4798
+ });
4799
+ return {
4800
+ environment_id: environment?.environment_id ?? (decl.environment ? state?.getResource({ type: "environment", name: decl.environment, provider })?.remote_id ?? void 0 : void 0),
4801
+ tunnel_id: tunnel?.tunnel_id,
4802
+ vault_ids: decl.vault ? [state?.getResource({ type: "vault", name: decl.vault, provider })?.remote_id ?? decl.vault] : [],
4803
+ skill_ids: skillIds
4804
+ };
4805
+ }
4806
+ function resolveDeploymentReferenceIds(decl, config, provider, state) {
4807
+ const agent = config.agents?.[decl.agent];
4808
+ const envName = decl.environment ?? agent?.environment;
4809
+ if (!envName) return void 0;
4810
+ const envDecl = config.environments?.[envName];
4811
+ return {
4812
+ environment_id: envDecl?.environment_id ?? (envDecl ? state?.getResource({ type: "environment", name: envName, provider })?.remote_id ?? void 0 : void 0)
4813
+ };
4814
+ }
4384
4815
  function getDeclaration(address, config) {
4385
4816
  return getResourceDeclaration(address, config);
4386
4817
  }
@@ -4402,15 +4833,65 @@ function computeSkillContentHash(source, basePath) {
4402
4833
  return "";
4403
4834
  }
4404
4835
 
4836
+ // src/internal/planner/plan-semantics.ts
4837
+ var NON_BLOCKING_ROOT_FIELDS = /* @__PURE__ */ new Set(["description", "metadata"]);
4838
+ function diffChangedPaths(before, after, prefix = "") {
4839
+ if (Object.is(before, after)) return [];
4840
+ if (Array.isArray(before) || Array.isArray(after)) {
4841
+ return structurallyEqual(before, after) ? [] : [prefix || "$root"];
4842
+ }
4843
+ if (isRecord(before) && isRecord(after)) {
4844
+ const paths = [];
4845
+ const keys = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
4846
+ for (const key of [...keys].sort()) {
4847
+ const path = prefix ? `${prefix}.${key}` : key;
4848
+ paths.push(...diffChangedPaths(before[key], after[key], path));
4849
+ }
4850
+ return paths;
4851
+ }
4852
+ return [prefix || "$root"];
4853
+ }
4854
+ function classifyReadinessImpact(action, changedPaths) {
4855
+ if (action === "no-op") return "none";
4856
+ if (action !== "update" || !changedPaths || changedPaths.length === 0) return "blocking";
4857
+ return changedPaths.every(isNonBlockingPath) ? "non_blocking" : "blocking";
4858
+ }
4859
+ function buildReadinessBaseline(declaration) {
4860
+ const record = isRecord(declaration) ? declaration : {};
4861
+ const { description: _description, metadata: _metadata, ...operational } = record;
4862
+ return {
4863
+ operational_hash: contentHash(operational),
4864
+ description_hash: contentHash(record.description ?? null),
4865
+ metadata_hash: contentHash(record.metadata ?? null)
4866
+ };
4867
+ }
4868
+ function diffReadinessBaseline(before, after) {
4869
+ const paths = [];
4870
+ if (before.operational_hash !== after.operational_hash) paths.push("$operational");
4871
+ if (before.description_hash !== after.description_hash) paths.push("description");
4872
+ if (before.metadata_hash !== after.metadata_hash) paths.push("metadata");
4873
+ return paths;
4874
+ }
4875
+ function isNonBlockingPath(path) {
4876
+ const root = path.split(".", 1)[0];
4877
+ return root !== void 0 && NON_BLOCKING_ROOT_FIELDS.has(root);
4878
+ }
4879
+ function isRecord(value) {
4880
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4881
+ }
4882
+ function structurallyEqual(left, right) {
4883
+ return contentHash(left) === contentHash(right);
4884
+ }
4885
+
4405
4886
  // src/internal/providers/drift-support.ts
4406
4887
  function supportsFullDrift(adapter, type) {
4407
4888
  return adapter.getDriftSupport?.(type) === "full" && typeof adapter.readComparableResource === "function";
4408
4889
  }
4409
4890
  async function readComparableIfSupported(adapter, type, id, name) {
4410
- const read = adapter.readComparableResource;
4411
- if (typeof read !== "function" || !supportsFullDrift(adapter, type)) return null;
4891
+ if (!supportsFullDrift(adapter, type)) return null;
4892
+ if (typeof adapter.readComparableResource !== "function") return null;
4412
4893
  try {
4413
- return await read(type, id, name);
4894
+ return await adapter.readComparableResource(type, id, name);
4414
4895
  } catch {
4415
4896
  return null;
4416
4897
  }
@@ -4473,6 +4954,22 @@ function resolveAgentRefs(agentName, config, provider, state) {
4473
4954
  }
4474
4955
  return refs;
4475
4956
  }
4957
+ function resolveTemplateRefs(agentName, config, provider, state) {
4958
+ const agent = config.agents?.[agentName];
4959
+ if (!agent) throw new UserError(`Agent '${agentName}' not found in config`);
4960
+ if (!agent.environment) {
4961
+ throw new UserError(`Forward template '${agentName}' must declare an environment.`);
4962
+ }
4963
+ const environment = config.environments?.[agent.environment];
4964
+ if (!environment) throw new UserError(`Environment '${agent.environment}' is not defined in config.`);
4965
+ const agentRefs = resolveAgentRefs(agentName, config, provider, state);
4966
+ return {
4967
+ ...agentRefs,
4968
+ environment_id: environment.environment_id ?? requireRef(state, { type: "environment", name: agent.environment, provider }),
4969
+ ...agent.tunnel ? { tunnel_id: resolveTunnelIdFromConfig(config, agent.tunnel, provider) } : {},
4970
+ vault_ids: agent.vault ? [requireRef(state, { type: "vault", name: agent.vault, provider })] : []
4971
+ };
4972
+ }
4476
4973
  function resolveDeploymentRefs(deploymentName, config, provider, state) {
4477
4974
  const dep = config.deployments?.[deploymentName];
4478
4975
  if (!dep) throw new UserError(`Deployment '${deploymentName}' not found in config`);
@@ -4491,11 +4988,17 @@ function resolveDeploymentRefs(deploymentName, config, provider, state) {
4491
4988
  `Deployment '${deploymentName}' has no environment and agent '${dep.agent}' does not declare one`
4492
4989
  );
4493
4990
  }
4494
- const environment_id = requireRef(state, {
4991
+ const envDecl = config.environments?.[envName];
4992
+ if (!envDecl) {
4993
+ throw new UserError(`Environment '${envName}' is not defined in config.`);
4994
+ }
4995
+ const environment_id = envDecl.environment_id ?? requireRef(state, {
4495
4996
  type: "environment",
4496
4997
  name: envName,
4497
4998
  provider
4498
4999
  });
5000
+ const tunnelName = dep.tunnel ?? agent.tunnel;
5001
+ const tunnel_id = tunnelName ? resolveTunnelIdFromConfig(config, tunnelName, provider) : void 0;
4499
5002
  const vaultNames = dep.vaults ?? (agent.vault ? [agent.vault] : []);
4500
5003
  const vault_ids = vaultNames.map((v) => requireRef(state, { type: "vault", name: v, provider }));
4501
5004
  const msNames = /* @__PURE__ */ new Set();
@@ -4518,10 +5021,21 @@ function resolveDeploymentRefs(deploymentName, config, provider, state) {
4518
5021
  agent_id,
4519
5022
  agent_version: dep.agent_version,
4520
5023
  environment_id,
5024
+ tunnel_id,
4521
5025
  vault_ids,
4522
5026
  memory_store_ids
4523
5027
  };
4524
5028
  }
5029
+ function resolveTunnelIdFromConfig(config, tunnelName, provider) {
5030
+ if (provider !== "qoder") {
5031
+ throw new UserError("Tunnels are supported only by Qoder BYOC sessions.");
5032
+ }
5033
+ const tunnel = config.tunnels?.[tunnelName];
5034
+ if (!tunnel) {
5035
+ throw new UserError(`Tunnel '${tunnelName}' is not defined in config. Declare it under the 'tunnels:' section.`);
5036
+ }
5037
+ return tunnel.tunnel_id;
5038
+ }
4525
5039
 
4526
5040
  // src/internal/executor/skill-resolver.ts
4527
5041
  import { readFileSync as readFileSync7, statSync as statSync3 } from "fs";
@@ -4562,6 +5076,17 @@ async function executePlan(plan, ctx, options = {}) {
4562
5076
  const concurrency = clampConcurrency(options.concurrency);
4563
5077
  const resultsByKey = /* @__PURE__ */ new Map();
4564
5078
  const failed = /* @__PURE__ */ new Set();
5079
+ let stateUpdated = false;
5080
+ for (const action of plan.actions) {
5081
+ if (action.address.type !== "environment") continue;
5082
+ const decl = ctx.config.environments?.[action.address.name];
5083
+ const existing = ctx.state.getResource(action.address);
5084
+ if (decl?.environment_id && existing && !existing.externally_managed) {
5085
+ ctx.state.setResource({ ...existing, externally_managed: true });
5086
+ stateUpdated = true;
5087
+ }
5088
+ }
5089
+ if (stateUpdated) await ctx.state.save();
4565
5090
  const actionable = plan.actions.filter((a) => a.action !== "no-op");
4566
5091
  const mutations = actionable.filter((a) => a.action !== "delete");
4567
5092
  const deletions = actionable.filter((a) => a.action === "delete");
@@ -4677,6 +5202,22 @@ function buildActionLevels(actions) {
4677
5202
  return levels;
4678
5203
  }
4679
5204
  async function executeAction(action, provider, ctx) {
5205
+ try {
5206
+ return await executeActionInner(action, provider, ctx);
5207
+ } catch (err) {
5208
+ if (action.action !== "update" || !ApiError.isNotFound(err)) throw err;
5209
+ emitRuntimeFeedback(ctx.onFeedback, {
5210
+ type: "resource_already_gone",
5211
+ level: "warning",
5212
+ action,
5213
+ resource: action.address,
5214
+ message: `update ${action.address.type}.${action.address.name} (${action.address.provider}) \u2014 not found remotely, recreating`
5215
+ });
5216
+ ctx.state.removeResource(action.address);
5217
+ return executeActionInner({ ...action, action: "create" }, provider, ctx);
5218
+ }
5219
+ }
5220
+ async function executeActionInner(action, provider, ctx) {
4680
5221
  const { address } = action;
4681
5222
  const { type, name } = address;
4682
5223
  let adopted = false;
@@ -4684,6 +5225,13 @@ async function executeAction(action, provider, ctx) {
4684
5225
  const existing = ctx.state.getResource(address);
4685
5226
  if (!existing) return false;
4686
5227
  const id = existing.remote_id;
5228
+ if (type === "environment") {
5229
+ const decl = ctx.config.environments?.[name];
5230
+ if (existing.externally_managed || decl?.environment_id) {
5231
+ ctx.state.removeResource(address);
5232
+ return false;
5233
+ }
5234
+ }
4687
5235
  if (id !== null) {
4688
5236
  try {
4689
5237
  switch (type) {
@@ -4699,6 +5247,11 @@ async function executeAction(action, provider, ctx) {
4699
5247
  case "agent":
4700
5248
  await provider.deleteAgent(id);
4701
5249
  break;
5250
+ case "template":
5251
+ if (!provider.archiveTemplate)
5252
+ throw new UserError(`Provider '${address.provider}' does not support templates`);
5253
+ await provider.archiveTemplate(id);
5254
+ break;
4702
5255
  case "memory_store":
4703
5256
  if (!provider.deleteMemoryStore) throw memoryStoreUnsupported(address.provider);
4704
5257
  await provider.deleteMemoryStore(id);
@@ -4730,7 +5283,22 @@ async function executeAction(action, provider, ctx) {
4730
5283
  case "environment": {
4731
5284
  const decl = ctx.config.environments[name];
4732
5285
  const remoteName = decl.name ?? name;
4733
- if (isUpdate) {
5286
+ if (decl.environment_id) {
5287
+ result = { id: decl.environment_id, type: "environment" };
5288
+ emitRuntimeFeedback(ctx.onFeedback, {
5289
+ type: "resource_action_success",
5290
+ level: "info",
5291
+ action,
5292
+ resource: action.address,
5293
+ message: `${action.action} ${action.address.type}.${action.address.name} (${action.address.provider}) \u2014 external reference, no remote mutation`
5294
+ });
5295
+ } else if (isUpdate) {
5296
+ const prior = ctx.state.getResource(address);
5297
+ if (prior?.externally_managed) {
5298
+ throw new UserError(
5299
+ `environment.${name} is recorded as an external reference (${prior.remote_id ?? "unknown id"}); refusing to modify it remotely. Restore 'environment_id' to keep it as a reference, or release it first with 'agents state rm environment.${name}' (then 'agents state import' to adopt it as a managed resource).`
5300
+ );
5301
+ }
4734
5302
  result = await provider.updateEnvironment(existingId, remoteName, decl);
4735
5303
  } else {
4736
5304
  try {
@@ -4860,6 +5428,29 @@ async function executeAction(action, provider, ctx) {
4860
5428
  }
4861
5429
  break;
4862
5430
  }
5431
+ case "template": {
5432
+ const createTemplate = provider.createTemplate?.bind(provider);
5433
+ const updateTemplate = provider.updateTemplate?.bind(provider);
5434
+ if (!createTemplate || !updateTemplate) {
5435
+ throw new UserError(`Provider '${address.provider}' does not support templates`);
5436
+ }
5437
+ const decl = ctx.config.agents[name];
5438
+ const remoteName = decl.name ?? name;
5439
+ const refs = resolveTemplateRefs(name, ctx.config, address.provider, ctx.state);
5440
+ if (isUpdate) {
5441
+ result = await updateTemplate(existingId, remoteName, decl, refs);
5442
+ } else {
5443
+ try {
5444
+ result = await createTemplate(remoteName, decl, refs);
5445
+ } catch (err) {
5446
+ result = await adoptOnConflict(err, address, provider, ctx.onFeedback, {
5447
+ onExisting: (existing) => updateTemplate(existing.id, remoteName, decl, refs)
5448
+ });
5449
+ adopted = true;
5450
+ }
5451
+ }
5452
+ break;
5453
+ }
4863
5454
  case "deployment": {
4864
5455
  const decl = ctx.config.deployments[name];
4865
5456
  const refs = resolveDeploymentRefs(name, ctx.config, address.provider, ctx.state);
@@ -4899,7 +5490,7 @@ async function executeAction(action, provider, ctx) {
4899
5490
  default:
4900
5491
  throw new UserError(`Unknown resource type: ${type}`);
4901
5492
  }
4902
- const hash = await computeResourceHash(address, ctx.config, ctx.configPath);
5493
+ const hash = await computeResourceHash(address, ctx.config, ctx.configPath, ctx.state);
4903
5494
  const comparableHash = computeComparableDesiredHash(address, ctx.config, provider);
4904
5495
  let remoteHash = comparableHash;
4905
5496
  let remoteSnapshot;
@@ -4908,15 +5499,19 @@ async function executeAction(action, provider, ctx) {
4908
5499
  remoteHash = contentHash(remote.comparable);
4909
5500
  remoteSnapshot = remote.snapshot ?? remote.comparable;
4910
5501
  }
5502
+ const priorResource = ctx.state.getResource(address);
4911
5503
  ctx.state.setResource({
4912
5504
  address,
4913
5505
  remote_id: result.id,
5506
+ externally_managed: priorResource?.externally_managed || type === "environment" && ctx.config.environments?.[name]?.environment_id ? true : void 0,
4914
5507
  version: result.version,
4915
5508
  content_hash: hash,
4916
5509
  desired_hash: hash,
4917
5510
  desired_comparable_hash: remoteHash,
5511
+ desired_readiness_baseline: buildReadinessBaseline(getResourceDeclaration(address, ctx.config)),
4918
5512
  remote_hash: remoteHash,
4919
5513
  remote_snapshot: remoteSnapshot,
5514
+ drift_paths: [],
4920
5515
  drift_status: remoteHash ? "in_sync" : void 0
4921
5516
  });
4922
5517
  return adopted;
@@ -5037,6 +5632,7 @@ function resolveTargetProviders(config) {
5037
5632
  }
5038
5633
  function collectReferenceDiagnostics(config, diagnostics) {
5039
5634
  const envNames = new Set(Object.keys(config.environments ?? {}));
5635
+ const tunnelNames = new Set(Object.keys(config.tunnels ?? {}));
5040
5636
  const skillNames = new Set(Object.keys(config.skills ?? {}));
5041
5637
  const vaultNames = new Set(Object.keys(config.vaults ?? {}));
5042
5638
  const memoryNames = new Set(Object.keys(config.memory_stores ?? {}));
@@ -5048,6 +5644,9 @@ function collectReferenceDiagnostics(config, diagnostics) {
5048
5644
  `agent.${name}: references unknown environment '${agent.environment}'`
5049
5645
  );
5050
5646
  }
5647
+ if (agent.tunnel && !tunnelNames.has(agent.tunnel)) {
5648
+ diagnostics.error("config.agent.tunnel.unknown", `agent.${name}: references unknown tunnel '${agent.tunnel}'`);
5649
+ }
5051
5650
  for (const skill of agent.skills ?? []) {
5052
5651
  if (typeof skill !== "string") continue;
5053
5652
  if (!skillNames.has(skill)) {
@@ -5079,6 +5678,14 @@ function collectReferenceDiagnostics(config, diagnostics) {
5079
5678
  }
5080
5679
  }
5081
5680
  }
5681
+ for (const [name, deployment] of Object.entries(config.deployments ?? {})) {
5682
+ if (deployment.tunnel && !tunnelNames.has(deployment.tunnel)) {
5683
+ diagnostics.error(
5684
+ "config.deployment.tunnel.unknown",
5685
+ `deployment.${name}: references unknown tunnel '${deployment.tunnel}'`
5686
+ );
5687
+ }
5688
+ }
5082
5689
  }
5083
5690
  function collectProviderCapabilities(config, providers, diagnostics) {
5084
5691
  for (const providerName of providers) {
@@ -5091,6 +5698,91 @@ function collectProviderCapabilities(config, providers, diagnostics) {
5091
5698
  continue;
5092
5699
  }
5093
5700
  const caps = def.capabilities;
5701
+ for (const [name, agent] of Object.entries(config.agents ?? {})) {
5702
+ if (agent.provider && agent.provider !== providerName) continue;
5703
+ const delivery = agent.delivery?.[providerName]?.type ?? "managed";
5704
+ if (delivery === "forward" && !isSupported(caps, "template")) {
5705
+ diagnostics.error(
5706
+ `${providerName}.agent.delivery.forward.unsupported`,
5707
+ `agent.${name}: provider '${providerName}' does not support delivery type 'forward'. Supported delivery types: managed.`,
5708
+ { type: "agent", name, provider: providerName }
5709
+ );
5710
+ }
5711
+ if (delivery === "forward" && providerName === "qoder") {
5712
+ if (!agent.environment) {
5713
+ diagnostics.error(
5714
+ "qoder.template.environment.required",
5715
+ `agent.${name}: Qoder Forward delivery requires an environment.`,
5716
+ { type: "template", name, provider: providerName }
5717
+ );
5718
+ }
5719
+ if (agent.memory_stores?.length) {
5720
+ diagnostics.error(
5721
+ "qoder.template.memory_store.unsupported",
5722
+ `agent.${name}: memory_stores are not yet supported by Qoder Forward Template delivery.`,
5723
+ { type: "template", name, provider: providerName }
5724
+ );
5725
+ }
5726
+ if (agent.multiagent) {
5727
+ diagnostics.error(
5728
+ "qoder.template.multiagent.unsupported",
5729
+ `agent.${name}: multiagent is not yet supported by Qoder Forward Template delivery.`,
5730
+ { type: "template", name, provider: providerName }
5731
+ );
5732
+ }
5733
+ }
5734
+ }
5735
+ if (providerName === "qoder") {
5736
+ for (const [name, deployment] of Object.entries(config.deployments ?? {})) {
5737
+ if (deployment.provider && deployment.provider !== providerName) continue;
5738
+ const tunnel = deployment.tunnel ?? config.agents?.[deployment.agent]?.tunnel;
5739
+ const referencedAgent = config.agents?.[deployment.agent];
5740
+ if (referencedAgent?.delivery?.qoder?.type === "forward") {
5741
+ diagnostics.error(
5742
+ "qoder.deployment.forward_template.unsupported",
5743
+ `deployment.${name}: managed deployments cannot reference Forward-delivered agent '${deployment.agent}'.`,
5744
+ { type: "deployment", name, provider: providerName }
5745
+ );
5746
+ }
5747
+ if (tunnel) {
5748
+ diagnostics.warning(
5749
+ `${providerName}.deployment.tunnel.unsupported`,
5750
+ `deployment.${name}: Qoder's deployment API does not accept tunnel_id; scheduled and triggered runs execute in the deployment's environment but without the BYOC tunnel. Create sessions directly for private-network MCP access.`,
5751
+ { type: "deployment", name, provider: providerName }
5752
+ );
5753
+ }
5754
+ }
5755
+ }
5756
+ if (providerName !== "qoder") {
5757
+ for (const [name, env] of Object.entries(config.environments ?? {})) {
5758
+ if (env.environment_id) continue;
5759
+ if (env.config.type === "self_hosted" && (!env.provider || env.provider === providerName)) {
5760
+ diagnostics.error(
5761
+ `${providerName}.environment.self_hosted.unsupported`,
5762
+ `environment.${name}: self_hosted environments are supported only by Qoder BYOC; use type 'cloud' or pin this environment to the qoder provider.`,
5763
+ { type: "environment", name, provider: providerName }
5764
+ );
5765
+ }
5766
+ }
5767
+ for (const [name, agent] of Object.entries(config.agents ?? {})) {
5768
+ if (agent.tunnel && (!agent.provider || agent.provider === providerName)) {
5769
+ diagnostics.error(
5770
+ `${providerName}.agent.tunnel.unsupported`,
5771
+ "Tunnels are supported only by Qoder BYOC sessions.",
5772
+ { type: "agent", name, provider: providerName }
5773
+ );
5774
+ }
5775
+ }
5776
+ for (const [name, deployment] of Object.entries(config.deployments ?? {})) {
5777
+ if (deployment.tunnel && (!deployment.provider || deployment.provider === providerName)) {
5778
+ diagnostics.error(
5779
+ `${providerName}.deployment.tunnel.unsupported`,
5780
+ "Tunnels are supported only by Qoder BYOC sessions.",
5781
+ { type: "deployment", name, provider: providerName }
5782
+ );
5783
+ }
5784
+ }
5785
+ }
5094
5786
  for (const missing of findMissingBailianMcpToolConfigs(config, providerName)) {
5095
5787
  diagnostics.error(
5096
5788
  `${providerName}.agent.mcp_toolkit_missing`,
@@ -5159,6 +5851,13 @@ function collectProviderCapabilities(config, providers, diagnostics) {
5159
5851
  }
5160
5852
  }
5161
5853
 
5854
+ // src/internal/core/agent-materialization.ts
5855
+ function resolveAgentMaterialization(provider, agent) {
5856
+ const mode = agent.delivery?.[provider]?.type ?? "managed";
5857
+ if (mode === "managed") return { resourceType: "agent", mode };
5858
+ return { resourceType: "template", mode };
5859
+ }
5860
+
5162
5861
  // src/internal/graph/dependency.ts
5163
5862
  function buildDependencyGraph(config, targetProviders) {
5164
5863
  const nodes = /* @__PURE__ */ new Map();
@@ -5216,7 +5915,8 @@ function buildDependencyGraph(config, targetProviders) {
5216
5915
  for (const name of Object.keys(config.agents)) {
5217
5916
  const decl = config.agents[name];
5218
5917
  if (decl.provider && decl.provider !== provider) continue;
5219
- const agentAddr = { type: "agent", name, provider };
5918
+ const materialization = resolveAgentMaterialization(provider, decl);
5919
+ const agentAddr = { type: materialization.resourceType, name, provider };
5220
5920
  addNode(agentAddr);
5221
5921
  if (decl.environment && config.environments?.[decl.environment]) {
5222
5922
  const envAddr = {
@@ -5258,7 +5958,9 @@ function buildDependencyGraph(config, targetProviders) {
5258
5958
  }
5259
5959
  if (decl.multiagent && isSupported(caps, "multiagent")) {
5260
5960
  for (const subName of decl.multiagent.agents) {
5261
- const subAddr = { type: "agent", name: subName, provider };
5961
+ const subDecl = config.agents[subName];
5962
+ const subType = subDecl ? resolveAgentMaterialization(provider, subDecl).resourceType : "agent";
5963
+ const subAddr = { type: subType, name: subName, provider };
5262
5964
  addEdge(agentAddr, subAddr);
5263
5965
  }
5264
5966
  }
@@ -5270,7 +5972,9 @@ function buildDependencyGraph(config, targetProviders) {
5270
5972
  if (decl.provider && decl.provider !== provider) continue;
5271
5973
  const depAddr = { type: "deployment", name, provider };
5272
5974
  addNode(depAddr);
5273
- const agentAddr = { type: "agent", name: decl.agent, provider };
5975
+ const agentDecl = config.agents?.[decl.agent];
5976
+ const agentType = agentDecl ? resolveAgentMaterialization(provider, agentDecl).resourceType : "agent";
5977
+ const agentAddr = { type: agentType, name: decl.agent, provider };
5274
5978
  if (nodes.has(addressKey(agentAddr))) addEdge(depAddr, agentAddr);
5275
5979
  if (decl.environment) {
5276
5980
  const envAddr = { type: "environment", name: decl.environment, provider };
@@ -5338,26 +6042,57 @@ async function buildPlan(config, state, options = {}) {
5338
6042
  for (const res of state.resources) {
5339
6043
  stateIndex.set(addressKey(res.address), res);
5340
6044
  }
6045
+ const remoteIdLookup = /* @__PURE__ */ new Map();
6046
+ for (const res of state.resources) {
6047
+ remoteIdLookup.set(addressKey(res.address), res);
6048
+ }
6049
+ const hashStateLookup = { getResource: (addr) => remoteIdLookup.get(addressKey(addr)) };
5341
6050
  for (const address of sorted) {
5342
6051
  const key = addressKey(address);
5343
- const desiredHash = await computeResourceHash(address, config, options.configPath);
6052
+ const desiredHash = await computeResourceHash(address, config, options.configPath, hashStateLookup);
5344
6053
  const existing = stateIndex.get(key);
5345
6054
  const deps = getDependencies(address, graph);
6055
+ if (address.type === "environment" && existing) {
6056
+ const envDecl = config.environments?.[address.name];
6057
+ if (existing.externally_managed && envDecl && !envDecl.environment_id) {
6058
+ diagnostics.error(
6059
+ "plan.environment.ownership_transition",
6060
+ `environment.${address.name} is recorded as an external reference (${existing.remote_id ?? "unknown id"}); removing 'environment_id' would make OpenCMA modify and eventually delete a remote environment it does not own. Restore 'environment_id' to keep it as a reference, or release it first with 'agents state rm environment.${address.name}' (then 'agents state import' to adopt the remote as a managed resource).`,
6061
+ address
6062
+ );
6063
+ stateIndex.delete(key);
6064
+ continue;
6065
+ }
6066
+ if (!existing.externally_managed && existing.remote_id && envDecl?.environment_id && envDecl.environment_id !== existing.remote_id) {
6067
+ diagnostics.warning(
6068
+ "plan.environment.ownership_orphan",
6069
+ `environment.${address.name}: switching to external reference '${envDecl.environment_id}' orphans the previously managed remote environment '${existing.remote_id}' \u2014 it will no longer be tracked or deletable by OpenCMA.`,
6070
+ address
6071
+ );
6072
+ }
6073
+ }
6074
+ const isExternalEnv = address.type === "environment" && Boolean(config.environments?.[address.name]?.environment_id);
6075
+ const createReason = isExternalEnv ? "Record external environment reference (no remote mutation)" : "Resource does not exist in state";
6076
+ const updateSuffix = isExternalEnv ? " \u2014 external reference, no remote mutation" : "";
5346
6077
  if (!existing) {
5347
6078
  actions.push({
5348
6079
  action: "create",
5349
6080
  address,
5350
6081
  driftKind: "none",
5351
- reason: "Resource does not exist in state",
6082
+ readinessImpact: "blocking",
6083
+ reason: createReason,
5352
6084
  after: { content_hash: desiredHash },
5353
6085
  dependencies: deps
5354
6086
  });
5355
6087
  } else if ((existing.desired_hash ?? existing.content_hash) !== desiredHash && existing.drift_status === "drifted") {
6088
+ const changedPaths = collectChangedPaths(address, config, existing, true);
5356
6089
  actions.push({
5357
6090
  action: "update",
5358
6091
  address,
5359
6092
  driftKind: "both",
5360
- reason: "Local config changed and remote drift detected",
6093
+ readinessImpact: classifyReadinessImpact("update", changedPaths),
6094
+ changedPaths,
6095
+ reason: `Local config changed and remote drift detected${updateSuffix}`,
5361
6096
  before: {
5362
6097
  content_hash: existing.desired_hash ?? existing.content_hash,
5363
6098
  remote_hash: existing.remote_hash,
@@ -5367,21 +6102,27 @@ async function buildPlan(config, state, options = {}) {
5367
6102
  dependencies: deps
5368
6103
  });
5369
6104
  } else if ((existing.desired_hash ?? existing.content_hash) !== desiredHash) {
6105
+ const changedPaths = collectChangedPaths(address, config, existing, false);
5370
6106
  actions.push({
5371
6107
  action: "update",
5372
6108
  address,
5373
6109
  driftKind: "local",
5374
- reason: "Local config changed",
6110
+ readinessImpact: classifyReadinessImpact("update", changedPaths),
6111
+ changedPaths,
6112
+ reason: `Local config changed${updateSuffix}`,
5375
6113
  before: { content_hash: existing.desired_hash ?? existing.content_hash },
5376
6114
  after: { content_hash: desiredHash },
5377
6115
  dependencies: deps
5378
6116
  });
5379
6117
  } else if (existing.drift_status === "drifted") {
6118
+ const changedPaths = existing.drift_paths;
5380
6119
  actions.push({
5381
6120
  action: "update",
5382
6121
  address,
5383
6122
  driftKind: "remote",
5384
- reason: "Remote drift detected",
6123
+ readinessImpact: classifyReadinessImpact("update", changedPaths),
6124
+ changedPaths,
6125
+ reason: `Remote drift detected${updateSuffix}`,
5385
6126
  before: {
5386
6127
  content_hash: existing.desired_hash ?? existing.content_hash,
5387
6128
  remote_hash: existing.remote_hash,
@@ -5395,6 +6136,7 @@ async function buildPlan(config, state, options = {}) {
5395
6136
  action: "no-op",
5396
6137
  address,
5397
6138
  driftKind: "none",
6139
+ readinessImpact: "none",
5398
6140
  reason: existing.drift_status === "unchecked" ? "No changes detected (remote content drift unchecked)" : "No changes detected",
5399
6141
  dependencies: deps
5400
6142
  });
@@ -5403,17 +6145,32 @@ async function buildPlan(config, state, options = {}) {
5403
6145
  }
5404
6146
  const toDelete = Array.from(stateIndex.values()).reverse();
5405
6147
  for (const res of toDelete) {
6148
+ const replacement = deliveryReplacementAddress(res.address, graph);
5406
6149
  actions.push({
5407
6150
  action: "delete",
5408
6151
  address: res.address,
5409
6152
  driftKind: "none",
5410
- reason: "Resource removed from configuration",
6153
+ readinessImpact: "blocking",
6154
+ reason: res.externally_managed ? "Remove local reference only \u2014 externally managed remote resource is left intact" : "Resource removed from configuration",
5411
6155
  before: { content_hash: res.desired_hash ?? res.content_hash },
5412
- dependencies: []
6156
+ dependencies: replacement ? [replacement] : []
5413
6157
  });
5414
6158
  }
5415
6159
  return { actions, diagnostics: diagnostics.getAll() };
5416
6160
  }
6161
+ function deliveryReplacementAddress(address, graph) {
6162
+ if (address.type !== "agent" && address.type !== "template") return void 0;
6163
+ const replacementType = address.type === "agent" ? "template" : "agent";
6164
+ const candidate = { ...address, type: replacementType };
6165
+ return graph.nodes.has(addressKey(candidate)) ? candidate : void 0;
6166
+ }
6167
+ function collectChangedPaths(address, config, existing, includeRemote) {
6168
+ const current = buildReadinessBaseline(getResourceDeclaration(address, config));
6169
+ const localPaths = existing.desired_readiness_baseline ? diffReadinessBaseline(existing.desired_readiness_baseline, current) : void 0;
6170
+ if (!includeRemote) return localPaths;
6171
+ if (!localPaths && !existing.drift_paths) return void 0;
6172
+ return [.../* @__PURE__ */ new Set([...localPaths ?? [], ...existing.drift_paths ?? []])].sort();
6173
+ }
5417
6174
  function getDependencies(address, graph) {
5418
6175
  const key = addressKey(address);
5419
6176
  const depKeys = graph.edges.get(key) ?? /* @__PURE__ */ new Set();
@@ -5454,16 +6211,19 @@ async function refreshState(state, providers, options = {}) {
5454
6211
  const decl = options.config ? getResourceDeclaration(res.address, options.config) : null;
5455
6212
  const desiredComparable = decl ? provider.normalizeDesiredResource(res.address.type, res.address.name, decl) : null;
5456
6213
  const desiredComparableHash = desiredComparable === null ? void 0 : contentHash(desiredComparable);
5457
- const baselineHash = res.desired_comparable_hash ?? desiredComparableHash;
6214
+ const baselineHash = res.desired_comparable_hash ?? desiredComparableHash ?? remoteHash;
5458
6215
  const driftStatus = baselineHash && remoteHash !== baselineHash ? "drifted" : "in_sync";
6216
+ const comparisonBaseline = res.remote_snapshot ?? desiredComparable;
6217
+ const driftPaths = driftStatus === "drifted" && comparisonBaseline != null ? diffChangedPaths(comparisonBaseline, remote2.comparable) : [];
5459
6218
  state.setResource({
5460
6219
  ...res,
5461
6220
  version: remote2.version ?? res.version,
5462
6221
  remote_id: remote2.id,
5463
6222
  desired_hash: res.desired_hash ?? res.content_hash,
5464
- desired_comparable_hash: res.desired_comparable_hash ?? desiredComparableHash,
6223
+ desired_comparable_hash: baselineHash,
5465
6224
  remote_hash: remoteHash,
5466
6225
  remote_snapshot: remote2.snapshot ?? remote2.comparable,
6226
+ drift_paths: driftPaths,
5467
6227
  drift_status: driftStatus
5468
6228
  });
5469
6229
  dirty = true;
@@ -5553,7 +6313,14 @@ async function syncProjectResourcesWithStateBackend(input, options = {}) {
5553
6313
  };
5554
6314
  });
5555
6315
  }
5556
- var IMPORTABLE_RESOURCE_TYPES = /* @__PURE__ */ new Set(["environment", "vault", "memory_store", "skill", "agent"]);
6316
+ var IMPORTABLE_RESOURCE_TYPES = /* @__PURE__ */ new Set([
6317
+ "environment",
6318
+ "vault",
6319
+ "memory_store",
6320
+ "skill",
6321
+ "agent",
6322
+ "template"
6323
+ ]);
5557
6324
  async function importResource(ctx, address, remoteId, options = {}) {
5558
6325
  if (!IMPORTABLE_RESOURCE_TYPES.has(address.type)) {
5559
6326
  throw new UserError(
@@ -5580,12 +6347,21 @@ async function importResource(ctx, address, remoteId, options = {}) {
5580
6347
  if (!contentHash2) {
5581
6348
  throw new UserError(`Planned ${address.type}.${address.name} is missing a content hash.`);
5582
6349
  }
6350
+ const provider = ctx.providers.get(address.provider);
6351
+ const remote = provider ? await readComparableIfSupported(provider, address.type, remoteId, address.name) : null;
6352
+ const remoteHash = remote ? contentHash(remote.comparable) : void 0;
5583
6353
  const resource = {
5584
6354
  address,
5585
6355
  remote_id: remoteId,
5586
- version: options.resourceVersion,
6356
+ externally_managed: address.type === "environment" && ctx.config.environments?.[address.name]?.environment_id ? true : void 0,
6357
+ version: options.resourceVersion ?? remote?.version,
5587
6358
  content_hash: contentHash2,
5588
- desired_hash: contentHash2
6359
+ desired_hash: contentHash2,
6360
+ desired_comparable_hash: remoteHash,
6361
+ desired_readiness_baseline: buildReadinessBaseline(getResourceDeclaration(address, ctx.config)),
6362
+ remote_hash: remoteHash,
6363
+ remote_snapshot: remote ? remote.snapshot ?? remote.comparable : void 0,
6364
+ drift_status: remoteHash ? "in_sync" : void 0
5589
6365
  };
5590
6366
  ctx.state.setResource(resource);
5591
6367
  await ctx.state.save();
@@ -5999,6 +6775,7 @@ function buildDeploymentContext(ctx, name, provider) {
5999
6775
  var destroyOrder = {
6000
6776
  deployment: 0,
6001
6777
  agent: 1,
6778
+ template: 1,
6002
6779
  skill: 2,
6003
6780
  memory_store: 3,
6004
6781
  vault: 4,
@@ -6034,6 +6811,10 @@ async function destroyPlannedProjectResources(planned, options = {}) {
6034
6811
  };
6035
6812
  }
6036
6813
  async function destroyOneResource(ctx, resource, options) {
6814
+ if (isExternalEnvironment(ctx, resource)) {
6815
+ ctx.state.removeResource(resource.address);
6816
+ return successResult(resource, "reference_removed");
6817
+ }
6037
6818
  let provider;
6038
6819
  try {
6039
6820
  provider = getRuntimeProvider(ctx, resource.address.provider);
@@ -6094,6 +6875,9 @@ async function destroyOneResource(ctx, resource, options) {
6094
6875
  return failureResult(resource, error);
6095
6876
  }
6096
6877
  }
6878
+ function isExternalEnvironment(ctx, resource) {
6879
+ return resource.address.type === "environment" && (resource.externally_managed || Boolean(ctx.config.environments?.[resource.address.name]?.environment_id));
6880
+ }
6097
6881
  function successResult(resource, reason) {
6098
6882
  return { resource, status: "success", reason };
6099
6883
  }
@@ -6110,6 +6894,10 @@ async function deleteRemoteResource(provider, type, id, cascade) {
6110
6894
  case "agent":
6111
6895
  await provider.deleteAgent(id);
6112
6896
  return;
6897
+ case "template":
6898
+ if (!provider.archiveTemplate) throw new UserError(`Provider does not support templates`);
6899
+ await provider.archiveTemplate(id);
6900
+ return;
6113
6901
  case "skill":
6114
6902
  await provider.deleteSkill(id);
6115
6903
  return;
@@ -6207,6 +6995,7 @@ function buildAgentDecl(base, input) {
6207
6995
  model,
6208
6996
  instructions: input.instructions ?? base?.instructions ?? "",
6209
6997
  ...input.environment ? { environment: input.environment } : {},
6998
+ ...input.vault ? { vault: input.vault } : {},
6210
6999
  provider: input.provider ?? base?.provider,
6211
7000
  tools: {
6212
7001
  ...base?.tools ?? { builtin: [] },
@@ -6244,6 +7033,18 @@ function buildSessionBindings(agentName, config, provider, state, options = {})
6244
7033
  const available = Object.keys(config.agents ?? {}).join(", ");
6245
7034
  throw new UserError(`Agent '${agentName}' not found in config. Available agents: ${available || "(none)"}`);
6246
7035
  }
7036
+ if (resolveAgentMaterialization(provider, agent).resourceType === "template") {
7037
+ const templateId = requireRef(state, { type: "template", name: agentName, provider });
7038
+ const identityId = options.identityId ?? config.defaults?.session?.qoder?.identity_id;
7039
+ return {
7040
+ delivery: "forward",
7041
+ template_id: templateId,
7042
+ ...identityId ? { identity_id: identityId } : {},
7043
+ files: (options.files ?? []).map((file) => ({ file_id: file.fileId, mount_path: file.mountPath })),
7044
+ title: options.title,
7045
+ metadata: options.metadata
7046
+ };
7047
+ }
6247
7048
  const agentId = requireRef(state, { type: "agent", name: agentName, provider });
6248
7049
  const agentState = state.getResource({ type: "agent", name: agentName, provider });
6249
7050
  const envName = options.environment ?? agent.environment;
@@ -6255,8 +7056,10 @@ function buildSessionBindings(agentName, config, provider, state, options = {})
6255
7056
  throw new UserError(`Agent '${agentName}' has no environment declared and --environment was not specified.`);
6256
7057
  }
6257
7058
  validateResourceInConfig(envName, "environment", config.environments);
6258
- environmentId = requireRef(state, { type: "environment", name: envName, provider });
7059
+ const envDecl = config.environments[envName];
7060
+ environmentId = envDecl.environment_id ?? requireRef(state, { type: "environment", name: envName, provider });
6259
7061
  }
7062
+ const tunnelId = resolveTunnelId(agent, config, options, provider);
6260
7063
  let vaultIds;
6261
7064
  if (options.vaultIds) {
6262
7065
  vaultIds = options.vaultIds;
@@ -6278,6 +7081,7 @@ function buildSessionBindings(agentName, config, provider, state, options = {})
6278
7081
  agent_id: agentId,
6279
7082
  agent_version: agentState?.version,
6280
7083
  environment_id: environmentId,
7084
+ tunnel_id: tunnelId,
6281
7085
  vault_ids: vaultIds,
6282
7086
  memory_store_ids: memoryStoreIds,
6283
7087
  files: (options.files ?? []).map((f) => ({ file_id: f.fileId, mount_path: f.mountPath })),
@@ -6285,6 +7089,19 @@ function buildSessionBindings(agentName, config, provider, state, options = {})
6285
7089
  metadata: options.metadata
6286
7090
  };
6287
7091
  }
7092
+ function resolveTunnelId(agent, config, options, provider) {
7093
+ const tunnelName = options.tunnel ?? agent.tunnel;
7094
+ if (!options.tunnelId && !tunnelName) return void 0;
7095
+ if (provider !== "qoder") {
7096
+ throw new UserError("Tunnels are supported only by Qoder BYOC sessions.");
7097
+ }
7098
+ if (options.tunnelId) return options.tunnelId;
7099
+ const tunnel = config.tunnels?.[tunnelName];
7100
+ if (!tunnel) {
7101
+ throw new UserError(`Tunnel '${tunnelName}' is not defined in config. Declare it under the 'tunnels:' section.`);
7102
+ }
7103
+ return tunnel.tunnel_id;
7104
+ }
6288
7105
  function validateResourceInConfig(name, type, resources) {
6289
7106
  if (!resources?.[name]) {
6290
7107
  throw new UserError(`${type} '${name}' is not defined in config.`);
@@ -6571,9 +7388,7 @@ function actionKey(action) {
6571
7388
  }
6572
7389
  function isNonBlockingAgentDrift(action) {
6573
7390
  if (action.action === "no-op") return true;
6574
- if (action.action !== "update") return false;
6575
- const reason = action.reason.toLowerCase();
6576
- return reason.includes("metadata") || reason.includes("description");
7391
+ return action.readinessImpact === "non_blocking";
6577
7392
  }
6578
7393
  function collectAgentAddresses(config, agentName, provider) {
6579
7394
  const agent = config.agents?.[agentName];
@@ -6581,7 +7396,10 @@ function collectAgentAddresses(config, agentName, provider) {
6581
7396
  throw new UserError(`Agent '${agentName}' not found in config.`);
6582
7397
  }
6583
7398
  const resolvedProvider = provider ?? resolveSessionProvider(agentName, config, void 0);
6584
- const addresses = [{ type: "agent", name: agentName, provider: resolvedProvider }];
7399
+ const materialization = resolveAgentMaterialization(resolvedProvider, agent);
7400
+ const addresses = [
7401
+ { type: materialization.resourceType, name: agentName, provider: resolvedProvider }
7402
+ ];
6585
7403
  if (agent.environment) {
6586
7404
  addresses.push({
6587
7405
  type: "environment",
@@ -6601,7 +7419,9 @@ function collectAgentAddresses(config, agentName, provider) {
6601
7419
  }
6602
7420
  }
6603
7421
  for (const subAgent of agent.multiagent?.agents ?? []) {
6604
- addresses.push({ type: "agent", name: subAgent, provider: resolvedProvider });
7422
+ const subDecl = config.agents?.[subAgent];
7423
+ const subType = subDecl ? resolveAgentMaterialization(resolvedProvider, subDecl).resourceType : "agent";
7424
+ addresses.push({ type: subType, name: subAgent, provider: resolvedProvider });
6605
7425
  }
6606
7426
  return addresses;
6607
7427
  }
@@ -6655,8 +7475,11 @@ function resolveSessionRuntime(ctx, target = {}) {
6655
7475
  async function createSessionForAgent(ctx, options = {}) {
6656
7476
  const { agentName, provider, adapter } = resolveSessionRuntime(ctx, options);
6657
7477
  const bindings = buildSessionBindings(agentName, ctx.config, provider, ctx.state, {
7478
+ identityId: options.identityId,
6658
7479
  environment: options.environment,
6659
7480
  environmentId: options.environmentId,
7481
+ tunnel: options.tunnel,
7482
+ tunnelId: options.tunnelId,
6660
7483
  vault: options.vault,
6661
7484
  vaultIds: options.vaultIds,
6662
7485
  memoryStores: options.memoryStores,
@@ -6670,8 +7493,11 @@ async function createSessionForAgent(ctx, options = {}) {
6670
7493
  async function startSessionRun(ctx, prompt, options = {}) {
6671
7494
  const { agentName, provider, adapter } = resolveSessionRuntime(ctx, options);
6672
7495
  const bindings = buildSessionBindings(agentName, ctx.config, provider, ctx.state, {
7496
+ identityId: options.identityId,
6673
7497
  environment: options.environment,
6674
7498
  environmentId: options.environmentId,
7499
+ tunnel: options.tunnel,
7500
+ tunnelId: options.tunnelId,
6675
7501
  vault: options.vault,
6676
7502
  vaultIds: options.vaultIds,
6677
7503
  memoryStores: options.memoryStores,
@@ -6764,13 +7590,17 @@ async function listSessionsForProject(ctx, options = {}) {
6764
7590
  const resolved = resolveSessionRuntime(ctx, options);
6765
7591
  provider = resolved.provider;
6766
7592
  agentName = resolved.agentName;
7593
+ const agentDecl = ctx.config.agents?.[resolved.agentName];
7594
+ const resourceType = agentDecl ? resolveAgentMaterialization(provider, agentDecl).resourceType : "agent";
6767
7595
  const state = ctx.state.getResource({
6768
- type: "agent",
7596
+ type: resourceType,
6769
7597
  name: resolved.agentName,
6770
7598
  provider
6771
7599
  });
6772
7600
  if (!state?.remote_id) {
6773
- throw new UserError(`Agent '${resolved.agentName}' not found in state. Run \`agents apply\` first.`);
7601
+ throw new UserError(
7602
+ `${resourceType === "template" ? "Template" : "Agent"} '${resolved.agentName}' not found in state. Run \`agents apply\` first.`
7603
+ );
6774
7604
  }
6775
7605
  agentId = state.remote_id;
6776
7606
  } else if (options.provider) {
@@ -6886,7 +7716,7 @@ function resolveDirectAdapter(ctx, overrideProvider) {
6886
7716
  function buildAgentNameByRemoteId(ctx, provider) {
6887
7717
  const names = /* @__PURE__ */ new Map();
6888
7718
  for (const resource of ctx.state.listResources()) {
6889
- if (resource.address.type === "agent" && resource.address.provider === provider && resource.remote_id) {
7719
+ if ((resource.address.type === "agent" || resource.address.type === "template") && resource.address.provider === provider && resource.remote_id) {
6890
7720
  names.set(resource.remote_id, resource.address.name);
6891
7721
  }
6892
7722
  }
@@ -7100,12 +7930,15 @@ var StateManager = class _StateManager {
7100
7930
  const resources = raw.map((r) => ({
7101
7931
  address: r.address,
7102
7932
  remote_id: r.remote_id,
7933
+ externally_managed: r.externally_managed === true ? true : void 0,
7103
7934
  version: r.version,
7104
7935
  content_hash: r.content_hash ?? r.desired_hash ?? "",
7105
7936
  desired_hash: r.desired_hash ?? r.content_hash ?? "",
7106
7937
  desired_comparable_hash: r.desired_comparable_hash,
7938
+ desired_readiness_baseline: r.desired_readiness_baseline,
7107
7939
  remote_hash: r.remote_hash,
7108
7940
  remote_snapshot: r.remote_snapshot,
7941
+ drift_paths: r.drift_paths,
7109
7942
  drift_status: r.drift_status
7110
7943
  }));
7111
7944
  return new _StateManager({ resources }, path);
@@ -7282,6 +8115,7 @@ var ResourceTypeSchema = z6.enum([
7282
8115
  "memory_store",
7283
8116
  "skill",
7284
8117
  "agent",
8118
+ "template",
7285
8119
  "deployment",
7286
8120
  "file"
7287
8121
  ]);
@@ -7299,11 +8133,14 @@ var DiagnosticSchema = z6.object({
7299
8133
  });
7300
8134
  var ActionTypeSchema = z6.enum(["create", "update", "delete", "no-op"]);
7301
8135
  var DriftKindSchema = z6.enum(["none", "local", "remote", "both"]);
8136
+ var PlanReadinessImpactSchema = z6.enum(["none", "non_blocking", "blocking"]);
7302
8137
  var PlannedActionSchema = z6.object({
7303
8138
  action: ActionTypeSchema,
7304
8139
  address: ResourceAddressSchema,
7305
8140
  reason: z6.string(),
7306
8141
  driftKind: DriftKindSchema.optional(),
8142
+ readinessImpact: PlanReadinessImpactSchema.optional(),
8143
+ changedPaths: z6.array(z6.string()).optional(),
7307
8144
  before: z6.record(z6.string(), z6.unknown()).optional(),
7308
8145
  after: z6.record(z6.string(), z6.unknown()).optional(),
7309
8146
  dependencies: z6.array(ResourceAddressSchema)
@@ -7556,6 +8393,7 @@ export {
7556
8393
  ListSessionsRequestSchema,
7557
8394
  ListSessionsResponseSchema,
7558
8395
  LocalFileStateBackend,
8396
+ PlanReadinessImpactSchema,
7559
8397
  PlannedActionSchema,
7560
8398
  ResourceAddressSchema,
7561
8399
  ResourceTypeSchema,