@openagentpack/sdk 0.3.1 → 0.4.0

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
@@ -58,10 +58,10 @@ var BaseApiClient = class {
58
58
  if (this.isConflict(res.status, body)) throw new ConflictError(res.status, body, this.errorPrefix);
59
59
  throw new ApiError(res.status, body, this.errorPrefix);
60
60
  }
61
- async post(path, body) {
61
+ async post(path, body, options) {
62
62
  const res = await resolveFetch()(`${this.baseUrl}${path}`, {
63
63
  method: "POST",
64
- headers: this.headers(),
64
+ headers: { ...this.headers(), ...options?.headers },
65
65
  body: JSON.stringify(body)
66
66
  });
67
67
  await this.throwIfError(res);
@@ -395,6 +395,24 @@ function skillNameFromFiles(files) {
395
395
  return match?.[1]?.replace(/^["']|["']$/g, "") || void 0;
396
396
  }
397
397
 
398
+ // src/internal/providers/deployment-conflict.ts
399
+ var DeploymentCreateConflictError = class extends ConflictError {
400
+ constructor(conflict, preparedFiles) {
401
+ super(conflict.statusCode, conflict.responseBody, "Deployment create");
402
+ this.preparedFiles = preparedFiles;
403
+ this.name = conflict.name;
404
+ this.message = conflict.message;
405
+ this.stack = conflict.stack;
406
+ }
407
+ preparedFiles;
408
+ };
409
+ function preserveDeploymentFilesOnConflict(error, preparedFiles) {
410
+ if (error instanceof ConflictError && preparedFiles.size > 0) {
411
+ throw new DeploymentCreateConflictError(error, preparedFiles);
412
+ }
413
+ throw error;
414
+ }
415
+
398
416
  // src/internal/providers/memory-api.ts
399
417
  function query(path, values) {
400
418
  const params = new URLSearchParams();
@@ -1736,11 +1754,15 @@ var ClaudeAdapter = class _ClaudeAdapter {
1736
1754
  async createDeployment(name, decl, refs, basePath) {
1737
1755
  const uploaded = await this.uploadDeploymentFiles(decl, basePath);
1738
1756
  const body = mapDeployment(name, decl, refs, this.projectName, uploaded);
1739
- const res = await this.client.post("/deployments", body);
1740
- return toRemoteResource(res);
1757
+ try {
1758
+ const res = await this.client.post("/deployments", body);
1759
+ return toRemoteResource(res);
1760
+ } catch (error) {
1761
+ preserveDeploymentFilesOnConflict(error, uploaded);
1762
+ }
1741
1763
  }
1742
- async updateDeployment(id, name, decl, refs, basePath) {
1743
- const uploaded = await this.uploadDeploymentFiles(decl, basePath);
1764
+ async updateDeployment(id, name, decl, refs, basePath, preparedFiles) {
1765
+ const uploaded = preparedFiles ? new Map(preparedFiles) : await this.uploadDeploymentFiles(decl, basePath);
1744
1766
  const current = await this.client.get(`/deployments/${id}`);
1745
1767
  if (current.schedule && !decl.schedule) {
1746
1768
  throw new UserError(
@@ -2013,12 +2035,23 @@ function normalizeToolNameForQoder(name) {
2013
2035
  function normalizeToolNameFromQoder(name) {
2014
2036
  return name.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
2015
2037
  }
2038
+ function normalizeEnvironmentPackages(value) {
2039
+ if (!value || typeof value !== "object") return void 0;
2040
+ const raw = value;
2041
+ const packages = {};
2042
+ for (const key of ["apt", "npm", "pip"]) {
2043
+ if (Array.isArray(raw[key]) && raw[key].length > 0) packages[key] = raw[key];
2044
+ }
2045
+ return Object.keys(packages).length > 0 ? packages : void 0;
2046
+ }
2016
2047
  function mapEnvironment2(name, decl, projectName) {
2017
2048
  const envType = decl.config.type ?? "cloud";
2018
2049
  const config = { type: envType };
2019
2050
  if (decl.config.networking) config.networking = decl.config.networking;
2020
2051
  else if (envType === "cloud") config.networking = { type: "unrestricted" };
2021
- if (decl.config.packages) config.packages = decl.config.packages;
2052
+ const packages = normalizeEnvironmentPackages(decl.config.packages);
2053
+ if (packages) config.packages = packages;
2054
+ if (decl.config.setup_script !== void 0) config.setup_script = decl.config.setup_script;
2022
2055
  return {
2023
2056
  name,
2024
2057
  description: decl.description ?? "",
@@ -2026,6 +2059,13 @@ function mapEnvironment2(name, decl, projectName) {
2026
2059
  metadata: injectMetadata(decl.metadata, projectName, name)
2027
2060
  };
2028
2061
  }
2062
+ function mapForwardEnvironment(name, decl, projectName) {
2063
+ const body = mapEnvironment2(name, decl, projectName);
2064
+ const config = { ...body.config };
2065
+ delete config.networking;
2066
+ body.config = config;
2067
+ return body;
2068
+ }
2029
2069
  function mapVault(name, decl, projectName) {
2030
2070
  const body = { display_name: decl.display_name };
2031
2071
  if (projectName) body.metadata = injectMetadata(decl.metadata, projectName, name);
@@ -2099,7 +2139,8 @@ function envToDecl2(raw) {
2099
2139
  config: {
2100
2140
  type: config.type ?? "cloud",
2101
2141
  networking: config.networking,
2102
- packages: config.packages
2142
+ packages: normalizeEnvironmentPackages(config.packages),
2143
+ setup_script: config.setup_script
2103
2144
  },
2104
2145
  metadata: stripAgentsMetadata(raw.metadata)
2105
2146
  });
@@ -2339,9 +2380,12 @@ function mapForwardTemplate(name, decl, refs, projectName) {
2339
2380
  environment_id: refs.environment_id,
2340
2381
  vault_ids: refs.vault_ids
2341
2382
  };
2383
+ body.files = Object.fromEntries((refs.file_ids ?? []).map((id) => [id, { enabled: true }]));
2342
2384
  if (refs.tunnel_id) body.tunnel_id = refs.tunnel_id;
2343
2385
  if (projectName) body.metadata = injectMetadata(decl.metadata, projectName, name);
2344
2386
  else body.metadata = decl.metadata ?? {};
2387
+ if (decl.environment_variables) body.environment_variables = decl.environment_variables;
2388
+ if (decl.managed_tool_config) body.managed_tool_config = decl.managed_tool_config;
2345
2389
  if (decl.tools) {
2346
2390
  body.tools = [
2347
2391
  {
@@ -2482,6 +2526,9 @@ function mapSession2(bindings) {
2482
2526
  if (bindings.tunnel_id) body.tunnel_id = bindings.tunnel_id;
2483
2527
  if (bindings.title) body.title = bindings.title;
2484
2528
  if (bindings.metadata) body.metadata = bindings.metadata;
2529
+ if (bindings.environment_variables) {
2530
+ body.environment_variables = Object.entries(bindings.environment_variables).map(([key, value]) => `${key}=${value}`).join(";");
2531
+ }
2485
2532
  if (bindings.vault_ids.length) body.vault_ids = bindings.vault_ids;
2486
2533
  const resources = [];
2487
2534
  for (const id of bindings.memory_store_ids) resources.push({ type: "memory_store", memory_store_id: id });
@@ -2527,6 +2574,7 @@ var QoderAdapter = class _QoderAdapter {
2527
2574
  client;
2528
2575
  memoryApi;
2529
2576
  forwardClient;
2577
+ forwardMemoryApi;
2530
2578
  projectName;
2531
2579
  forwardSessionIds = /* @__PURE__ */ new Set();
2532
2580
  constructor(apiKey, gateway, projectName, forwardGateway) {
@@ -2548,6 +2596,19 @@ var QoderAdapter = class _QoderAdapter {
2548
2596
  apiKey,
2549
2597
  gateway: forwardGateway ?? deriveForwardGateway(gateway)
2550
2598
  });
2599
+ this.forwardMemoryApi = new ProviderMemoryApi(this.forwardClient, {
2600
+ pathStyle: "relative",
2601
+ cursorParam: "after_id",
2602
+ updatePrecondition: "content_sha256",
2603
+ prefixParam: "prefix",
2604
+ versionsSegment: "versions",
2605
+ storeMetadataMode: "merge_patch",
2606
+ supportsView: false,
2607
+ supportsMemoryMetadata: true,
2608
+ supportsPathUpdate: false,
2609
+ supportsDeletePrecondition: false,
2610
+ supportsIncludeArchived: true
2611
+ });
2551
2612
  this.projectName = projectName ?? "";
2552
2613
  }
2553
2614
  async validate() {
@@ -2562,7 +2623,7 @@ var QoderAdapter = class _QoderAdapter {
2562
2623
  file: "/files",
2563
2624
  deployment: "/deployments"
2564
2625
  };
2565
- async findResource(type, name, id) {
2626
+ async findResource(type, name, id, mode) {
2566
2627
  if (type === "template") {
2567
2628
  const raw2 = await locateRemote(this.forwardClient, "/templates", name, id, (item) => item.status !== "archived");
2568
2629
  return raw2 ? toRemoteResource(raw2) : null;
@@ -2582,7 +2643,14 @@ var QoderAdapter = class _QoderAdapter {
2582
2643
  const raw2 = await locateRemote(this.forwardClient, "/channels", name, id, () => true);
2583
2644
  return raw2 ? toRemoteResource(raw2) : null;
2584
2645
  }
2585
- const raw = await locateRemote(this.client, _QoderAdapter.ENDPOINT_MAP[type], name, id, notArchived);
2646
+ if (type === "environment" && id && mode === "auto") {
2647
+ const managed = await locateRemote(this.client, "/environments", name, id, notArchived);
2648
+ if (managed) return toRemoteResource(managed);
2649
+ const forward = await locateRemote(this.forwardClient, "/environments", name, id, notArchived);
2650
+ return forward ? toRemoteResource(forward) : null;
2651
+ }
2652
+ const client = mode === "forward" && (type === "environment" || type === "skill" || type === "vault" || type === "memory_store" || type === "file") ? this.forwardClient : this.client;
2653
+ const raw = await locateRemote(client, _QoderAdapter.ENDPOINT_MAP[type], name, id, notArchived);
2586
2654
  return raw ? toRemoteResource(raw) : null;
2587
2655
  }
2588
2656
  async listAgents(filter) {
@@ -2691,14 +2759,10 @@ var QoderAdapter = class _QoderAdapter {
2691
2759
  }
2692
2760
  normalizeRemote(type, raw) {
2693
2761
  if (type === "environment") {
2694
- const config = raw.config ?? {};
2762
+ const normalized = envToDecl2(raw);
2695
2763
  return compactDeep({
2696
2764
  description: raw.description,
2697
- config: {
2698
- type: config.type ?? "cloud",
2699
- networking: config.networking,
2700
- packages: config.packages
2701
- },
2765
+ config: normalized.config,
2702
2766
  metadata: stripAgentsMetadata(raw.metadata)
2703
2767
  });
2704
2768
  }
@@ -2730,16 +2794,21 @@ var QoderAdapter = class _QoderAdapter {
2730
2794
  }
2731
2795
  if (type === "channel") {
2732
2796
  const channelConfig = raw.channel_config ?? {};
2733
- return compactDeep({
2734
- identity_id: raw.identity_id,
2735
- template_id: raw.template_id,
2797
+ const mode = raw.identity_resolution?.mode ?? "fixed";
2798
+ const normalized = {
2799
+ identity_resolution: { mode },
2736
2800
  channel_type: raw.channel_type,
2737
2801
  name: raw.name,
2738
2802
  enabled: raw.enabled,
2739
2803
  channel_config: {
2740
2804
  response_options: channelConfig.response_options ?? {}
2741
2805
  }
2742
- });
2806
+ };
2807
+ if (mode === "fixed") {
2808
+ normalized.identity_id = raw.identity_id;
2809
+ normalized.template_id = raw.template_id;
2810
+ }
2811
+ return compactDeep(normalized);
2743
2812
  }
2744
2813
  return compactDeep({
2745
2814
  description: raw.description,
@@ -2750,17 +2819,29 @@ var QoderAdapter = class _QoderAdapter {
2750
2819
  metadata: stripAgentsMetadata(raw.metadata)
2751
2820
  });
2752
2821
  }
2753
- async createEnvironment(name, decl) {
2754
- const body = mapEnvironment2(name, decl, this.projectName);
2755
- const res = await this.client.post("/environments", body);
2822
+ async createEnvironment(name, decl, mode = "managed") {
2823
+ const body = mode === "forward" ? mapForwardEnvironment(name, decl, this.projectName) : mapEnvironment2(name, decl, this.projectName);
2824
+ const res = await (mode === "forward" ? this.forwardClient : this.client).post("/environments", body);
2756
2825
  return toRemoteResource(res);
2757
2826
  }
2758
- async updateEnvironment(id, name, decl) {
2759
- const body = mapEnvironment2(name, decl, this.projectName);
2760
- const res = await this.client.put(`/environments/${id}`, body);
2827
+ async updateEnvironment(id, name, decl, mode = "managed") {
2828
+ const body = mode === "forward" ? mapForwardEnvironment(name, decl, this.projectName) : mapEnvironment2(name, decl, this.projectName);
2829
+ const client = mode === "forward" ? this.forwardClient : this.client;
2830
+ const current = await client.get(`/environments/${id}`);
2831
+ const currentMetadata = current.metadata ?? {};
2832
+ const metadata = { ...body.metadata ?? {} };
2833
+ for (const key of Object.keys(currentMetadata)) {
2834
+ if (!key.startsWith("agents.") && !(key in metadata)) metadata[key] = null;
2835
+ }
2836
+ body.metadata = metadata;
2837
+ const res = await client.post(`/environments/${id}`, body);
2761
2838
  return toRemoteResource(res);
2762
2839
  }
2763
- async deleteEnvironment(id, cascade = false) {
2840
+ async deleteEnvironment(id, cascade = false, mode = "managed") {
2841
+ if (mode === "forward") {
2842
+ await this.forwardClient.delete(`/environments/${id}`);
2843
+ return;
2844
+ }
2764
2845
  try {
2765
2846
  await this.client.delete(`/environments/${id}`);
2766
2847
  return;
@@ -2787,17 +2868,18 @@ var QoderAdapter = class _QoderAdapter {
2787
2868
  await this.client.post(`/environments/${id}/archive`, {});
2788
2869
  }
2789
2870
  }
2790
- async createVault(name, decl) {
2871
+ async createVault(name, decl, mode = "managed") {
2872
+ const client = mode === "forward" ? this.forwardClient : this.client;
2791
2873
  const body = mapVault(name, decl, this.projectName);
2792
- const res = await this.client.post("/vaults", body);
2874
+ const res = await client.post("/vaults", body);
2793
2875
  const vaultId = res.id;
2794
2876
  for (const cred of decl.credentials ?? []) {
2795
- await this.client.post(`/vaults/${vaultId}/credentials`, mapCredential(cred));
2877
+ await client.post(`/vaults/${vaultId}/credentials`, mapCredential(cred));
2796
2878
  }
2797
2879
  return toRemoteResource(res);
2798
2880
  }
2799
- async deleteVault(id) {
2800
- await this.client.delete(`/vaults/${id}`);
2881
+ async deleteVault(id, mode = "managed") {
2882
+ await (mode === "forward" ? this.forwardClient : this.client).delete(`/vaults/${id}`);
2801
2883
  }
2802
2884
  async exportResources(type) {
2803
2885
  return exportRemoteResources(this.client, type, {
@@ -2808,17 +2890,24 @@ var QoderAdapter = class _QoderAdapter {
2808
2890
  agentToDecl: agentToDecl2
2809
2891
  });
2810
2892
  }
2811
- async createSkill(name, decl, files) {
2893
+ async createSkill(name, decl, files, mode = "managed") {
2812
2894
  const formData = await buildSkillFormData(name, decl, files);
2813
- const res = await this.client.postFormData("/skills", formData);
2895
+ const res = await (mode === "forward" ? this.forwardClient : this.client).postFormData(
2896
+ "/skills",
2897
+ formData
2898
+ );
2814
2899
  return toRemoteResource(res);
2815
2900
  }
2816
- async updateSkill(id, name, decl, files) {
2817
- await this.client.delete(`/skills/${id}`);
2818
- return this.createSkill(name, decl, files);
2901
+ async updateSkill(id, name, decl, files, mode = "managed") {
2902
+ const client = mode === "forward" ? this.forwardClient : this.client;
2903
+ const packageName = skillNameFromFiles(files) ?? name;
2904
+ const formData = await buildSkillFormData(packageName, decl, files, "files", false, true);
2905
+ await client.postFormData(`/skills/${id}/versions`, formData);
2906
+ const current = await client.get(`/skills/${id}`);
2907
+ return toRemoteResource(current);
2819
2908
  }
2820
- async deleteSkill(id) {
2821
- await this.client.delete(`/skills/${id}`);
2909
+ async deleteSkill(id, mode = "managed") {
2910
+ await (mode === "forward" ? this.forwardClient : this.client).delete(`/skills/${id}`);
2822
2911
  }
2823
2912
  async createAgent(name, decl, refs) {
2824
2913
  const body = mapAgent2(name, decl, refs, void 0, this.projectName);
@@ -2835,21 +2924,89 @@ var QoderAdapter = class _QoderAdapter {
2835
2924
  await this.client.delete(`/agents/${id}`);
2836
2925
  }
2837
2926
  async createTemplate(name, decl, refs) {
2838
- await this.registerForwardVaults(refs.vault_ids);
2839
2927
  const body = mapForwardTemplate(name, decl, refs, this.projectName);
2840
2928
  const res = await this.forwardClient.post("/templates", body);
2929
+ await this.reconcileForwardMemoryMounts(res.id, refs);
2841
2930
  return toRemoteResource(res);
2842
2931
  }
2843
2932
  async updateTemplate(id, name, decl, refs) {
2844
- await this.registerForwardVaults(refs.vault_ids);
2845
2933
  const body = mapForwardTemplate(name, decl, refs, this.projectName);
2846
2934
  if (!refs.tunnel_id) body.tunnel_id = null;
2847
2935
  const res = await this.forwardClient.post(`/templates/${id}`, body);
2936
+ await this.reconcileForwardMemoryMounts(id, refs);
2848
2937
  return toRemoteResource(res);
2849
2938
  }
2850
- async archiveTemplate(id) {
2939
+ async archiveTemplate(id, ownedMemoryStoreIds = []) {
2940
+ const owned = new Set(ownedMemoryStoreIds);
2941
+ if (owned.size > 0) {
2942
+ for (const identity of await this.forwardClient.getAllPaged("/identities")) {
2943
+ if (typeof identity.id !== "string") continue;
2944
+ const path = `/identities/${identity.id}/templates/${id}/memory_stores`;
2945
+ try {
2946
+ const mounts = await this.forwardClient.get(path);
2947
+ for (const mount of mounts.data ?? []) {
2948
+ const storeId = mount.memory_store_id;
2949
+ if (mount.system_managed !== true && typeof storeId === "string" && owned.has(storeId)) {
2950
+ await this.forwardClient.delete(`${path}/${storeId}`);
2951
+ }
2952
+ }
2953
+ } catch (error) {
2954
+ if (!ApiError.isNotFound(error)) throw error;
2955
+ }
2956
+ }
2957
+ }
2851
2958
  await this.forwardClient.post(`/templates/${id}/archive`, {});
2852
2959
  }
2960
+ async reconcileForwardMemoryMounts(templateId, refs) {
2961
+ if (refs.memory_store_ids === void 0) return;
2962
+ const desired = new Set(refs.memory_store_ids ?? []);
2963
+ if (!refs.identity_id) {
2964
+ if (desired.size > 0) throw new UserError("Qoder Forward Memory Store mounts require an Identity.");
2965
+ return;
2966
+ }
2967
+ const path = `/identities/${refs.identity_id}/templates/${templateId}/memory_stores`;
2968
+ const current = await this.forwardClient.get(path);
2969
+ const explicit = (current.data ?? []).filter((mount) => mount.system_managed !== true);
2970
+ const owned = new Set(refs.owned_memory_store_ids ?? refs.memory_store_ids ?? []);
2971
+ for (const mount of explicit) {
2972
+ const storeId = mount.memory_store_id;
2973
+ if (typeof storeId === "string" && owned.has(storeId) && !desired.has(storeId)) {
2974
+ await this.forwardClient.delete(`${path}/${storeId}`);
2975
+ }
2976
+ }
2977
+ const mounted = new Set(
2978
+ explicit.map((mount) => mount.memory_store_id).filter((id) => typeof id === "string")
2979
+ );
2980
+ for (const memoryStoreId of desired) {
2981
+ if (!mounted.has(memoryStoreId)) await this.forwardClient.post(path, { memory_store_id: memoryStoreId });
2982
+ }
2983
+ }
2984
+ async reconcileDefaultMemoryStore(identityId, templateId, desired) {
2985
+ const storeId = await this.findDefaultMemoryStoreId(identityId, templateId);
2986
+ if (!storeId) return { status: "pending" };
2987
+ const current = await this.forwardClient.get(`/memory_stores/${storeId}`);
2988
+ const descriptionChanged = desired.description !== void 0 && current.description !== desired.description;
2989
+ if (current.name === desired.name && !descriptionChanged) {
2990
+ return { status: "unchanged", memory_store_id: storeId };
2991
+ }
2992
+ await this.forwardClient.post(`/memory_stores/${storeId}`, {
2993
+ name: desired.name,
2994
+ ...desired.description !== void 0 ? { description: desired.description } : {}
2995
+ });
2996
+ return { status: "updated", memory_store_id: storeId };
2997
+ }
2998
+ async findDefaultMemoryStoreId(identityId, templateId) {
2999
+ const mounts = await this.forwardClient.get(
3000
+ `/identities/${identityId}/templates/${templateId}/memory_stores`
3001
+ );
3002
+ const writableDefault = (mounts.data ?? []).find(
3003
+ (mount) => mount.system_managed === true && mount.access === "read_write"
3004
+ );
3005
+ return typeof writableDefault?.memory_store_id === "string" ? writableDefault.memory_store_id : null;
3006
+ }
3007
+ async deleteDefaultMemoryStore(id) {
3008
+ await this.client.delete(`/memory_stores/${id}`);
3009
+ }
2853
3010
  async createIdentity(name, decl) {
2854
3011
  if (decl.identity_id) return { id: decl.identity_id, type: "identity" };
2855
3012
  const res = await this.forwardClient.post("/identities", {
@@ -2886,12 +3043,14 @@ var QoderAdapter = class _QoderAdapter {
2886
3043
  }
2887
3044
  async updateChannel(id, name, decl, refs) {
2888
3045
  const current = await this.forwardClient.get(`/channels/${id}`);
2889
- if (current.channel_type !== decl.type) {
3046
+ const currentMode = current.identity_resolution?.mode ?? "fixed";
3047
+ if (current.channel_type !== decl.type || currentMode !== (decl.mode ?? "fixed")) {
2890
3048
  await this.deleteChannel(id);
2891
3049
  return this.createChannel(name, decl, refs);
2892
3050
  }
2893
3051
  const body = this.mapChannel(name, decl, refs);
2894
3052
  delete body.channel_type;
3053
+ delete body.identity_resolution;
2895
3054
  const res = await this.forwardClient.post(`/channels/${id}`, body);
2896
3055
  return toRemoteResource(res);
2897
3056
  }
@@ -2899,9 +3058,8 @@ var QoderAdapter = class _QoderAdapter {
2899
3058
  await this.forwardClient.delete(`/channels/${id}`);
2900
3059
  }
2901
3060
  mapChannel(name, decl, refs) {
2902
- return {
2903
- identity_id: refs.identity_id,
2904
- template_id: refs.agent_id,
3061
+ const mode = decl.mode ?? "fixed";
3062
+ const body = {
2905
3063
  channel_type: decl.type,
2906
3064
  name: decl.name ?? name,
2907
3065
  enabled: decl.enabled ?? true,
@@ -2914,22 +3072,27 @@ var QoderAdapter = class _QoderAdapter {
2914
3072
  }
2915
3073
  }
2916
3074
  };
2917
- }
2918
- async registerForwardVaults(vaultIds) {
2919
- for (const id of vaultIds) {
2920
- await this.forwardClient.post("/resources/registry", {
2921
- type: "vault",
2922
- resource: { id }
2923
- });
3075
+ if (mode === "pairing") {
3076
+ body.identity_resolution = { mode: "pairing" };
3077
+ } else {
3078
+ body.identity_id = refs.identity_id;
3079
+ body.template_id = refs.agent_id;
2924
3080
  }
3081
+ return body;
2925
3082
  }
2926
- async createMemoryStore(name, decl) {
3083
+ async createMemoryStore(name, decl, mode = "managed") {
2927
3084
  const body = mapMemoryStore2(name, decl);
2928
- const res = await this.client.post("/memory_stores", body);
3085
+ const client = mode === "forward" ? this.forwardClient : this.client;
3086
+ const memoryApi = mode === "forward" ? this.forwardMemoryApi : this.memoryApi;
3087
+ const res = await client.post(
3088
+ "/memory_stores",
3089
+ body,
3090
+ mode === "forward" ? { headers: { "Idempotency-Key": crypto.randomUUID() } } : void 0
3091
+ );
2929
3092
  const storeId = res.id;
2930
3093
  try {
2931
3094
  for (const entry of decl.entries ?? []) {
2932
- await this.memoryApi.createMemory(storeId, { content: entry.content, path: entry.key });
3095
+ await memoryApi.createMemory(storeId, { content: entry.content, path: entry.key });
2933
3096
  }
2934
3097
  } catch (error) {
2935
3098
  await this.client.delete(`/memory_stores/${storeId}`).catch(() => void 0);
@@ -2937,7 +3100,7 @@ var QoderAdapter = class _QoderAdapter {
2937
3100
  }
2938
3101
  return toRemoteResource(res);
2939
3102
  }
2940
- async deleteMemoryStore(id) {
3103
+ async deleteMemoryStore(id, _mode = "managed") {
2941
3104
  await this.client.delete(`/memory_stores/${id}`);
2942
3105
  }
2943
3106
  listMemoryStores(options) {
@@ -2946,23 +3109,23 @@ var QoderAdapter = class _QoderAdapter {
2946
3109
  getMemoryStore(id) {
2947
3110
  return this.memoryApi.getStore(id);
2948
3111
  }
2949
- updateMemoryStore(id, input) {
2950
- return this.memoryApi.updateStore(id, input);
3112
+ updateMemoryStore(id, input, mode = "managed") {
3113
+ return (mode === "forward" ? this.forwardMemoryApi : this.memoryApi).updateStore(id, input);
2951
3114
  }
2952
3115
  archiveMemoryStore(id) {
2953
3116
  return this.memoryApi.archiveStore(id);
2954
3117
  }
2955
- createMemory(storeId, input) {
2956
- return this.memoryApi.createMemory(storeId, input);
3118
+ createMemory(storeId, input, mode = "managed") {
3119
+ return (mode === "forward" ? this.forwardMemoryApi : this.memoryApi).createMemory(storeId, input);
2957
3120
  }
2958
- listMemories(storeId, options) {
2959
- return this.memoryApi.listMemories(storeId, options);
3121
+ listMemories(storeId, options, mode = "managed") {
3122
+ return (mode === "forward" ? this.forwardMemoryApi : this.memoryApi).listMemories(storeId, options);
2960
3123
  }
2961
3124
  getMemory(storeId, memoryId) {
2962
3125
  return this.memoryApi.getMemory(storeId, memoryId);
2963
3126
  }
2964
- updateMemory(storeId, memoryId, input) {
2965
- return this.memoryApi.updateMemory(storeId, memoryId, input);
3127
+ updateMemory(storeId, memoryId, input, mode = "managed") {
3128
+ return (mode === "forward" ? this.forwardMemoryApi : this.memoryApi).updateMemory(storeId, memoryId, input);
2966
3129
  }
2967
3130
  deleteMemory(storeId, memoryId, expected) {
2968
3131
  return this.memoryApi.deleteMemory(storeId, memoryId, expected);
@@ -2979,11 +3142,15 @@ var QoderAdapter = class _QoderAdapter {
2979
3142
  async createDeployment(name, decl, refs, basePath) {
2980
3143
  const uploaded = await this.uploadDeploymentFiles(decl, basePath);
2981
3144
  const body = mapDeployment2(name, decl, refs, this.projectName, uploaded);
2982
- const res = await this.client.post("/deployments", body);
2983
- return toRemoteResource(res);
3145
+ try {
3146
+ const res = await this.client.post("/deployments", body);
3147
+ return toRemoteResource(res);
3148
+ } catch (error) {
3149
+ preserveDeploymentFilesOnConflict(error, uploaded);
3150
+ }
2984
3151
  }
2985
- async updateDeployment(id, name, decl, refs, basePath) {
2986
- const uploaded = await this.uploadDeploymentFiles(decl, basePath);
3152
+ async updateDeployment(id, name, decl, refs, basePath, preparedFiles) {
3153
+ const uploaded = preparedFiles ? new Map(preparedFiles) : await this.uploadDeploymentFiles(decl, basePath);
2987
3154
  const current = await this.client.get(`/deployments/${id}`);
2988
3155
  if (current.schedule && !decl.schedule) {
2989
3156
  throw new UserError(
@@ -3091,6 +3258,9 @@ var QoderAdapter = class _QoderAdapter {
3091
3258
  };
3092
3259
  if (bindings.title) body2.title = bindings.title;
3093
3260
  if (bindings.metadata) body2.metadata = bindings.metadata;
3261
+ if (bindings.environment_variables) {
3262
+ body2.config = { environment_variables: bindings.environment_variables };
3263
+ }
3094
3264
  if (bindings.files?.length) {
3095
3265
  body2.resources = bindings.files.map((file) => ({
3096
3266
  type: "file",
@@ -3244,30 +3414,42 @@ var QoderAdapter = class _QoderAdapter {
3244
3414
  return res.data;
3245
3415
  }
3246
3416
  // --- Files ---
3247
- async uploadFile(filePath, options) {
3417
+ async uploadFile(filePath, options, mode = "managed") {
3418
+ if (mode === "forward" && options?.purpose && !["user_upload", "session_resource"].includes(options.purpose)) {
3419
+ throw new UserError(
3420
+ `Qoder Forward File purpose must be 'user_upload' or 'session_resource', got '${options.purpose}'.`
3421
+ );
3422
+ }
3248
3423
  const resolved = resolve2(filePath);
3249
3424
  const content = readFileSync2(resolved);
3250
3425
  const fileName = options?.name ?? basename2(resolved);
3251
- return this.uploadFileContent(new Uint8Array(content), fileName, {
3252
- purpose: options?.purpose
3253
- });
3426
+ const formData = buildFileFormData(new Uint8Array(content), fileName, { purpose: options?.purpose });
3427
+ const res = await (mode === "forward" ? this.forwardClient : this.client).postFormData(
3428
+ "/files",
3429
+ formData
3430
+ );
3431
+ return toRestFileInfo(res);
3254
3432
  }
3255
3433
  async uploadFileContent(content, filename, options) {
3256
- const formData = new FormData();
3257
- const bytes = new Uint8Array(content);
3258
- formData.append(
3259
- "file",
3260
- options?.mimeType ? new File([bytes], filename, { type: options.mimeType }) : new File([bytes], filename)
3261
- );
3262
- if (filename) formData.append("name", filename);
3263
- if (options?.purpose) formData.append("purpose", options.purpose);
3434
+ const formData = buildFileFormData(content, filename, options);
3264
3435
  const res = await this.client.postFormData("/files", formData);
3265
3436
  return toRestFileInfo(res);
3266
3437
  }
3267
- async deleteFile(id) {
3268
- await this.client.delete(`/files/${id}`);
3438
+ async deleteFile(id, mode = "managed") {
3439
+ await (mode === "forward" ? this.forwardClient : this.client).delete(`/files/${id}`);
3269
3440
  }
3270
3441
  };
3442
+ function buildFileFormData(content, filename, options) {
3443
+ const formData = new FormData();
3444
+ const bytes = new Uint8Array(content);
3445
+ formData.append(
3446
+ "file",
3447
+ options?.mimeType ? new File([bytes], filename, { type: options.mimeType }) : new File([bytes], filename)
3448
+ );
3449
+ if (filename) formData.append("name", filename);
3450
+ if (options?.purpose) formData.append("purpose", options.purpose);
3451
+ return formData;
3452
+ }
3271
3453
  function toSessionInfo2(res) {
3272
3454
  return buildSessionInfo(res, (r) => r.memory_store_ids ?? []);
3273
3455
  }
@@ -3317,22 +3499,24 @@ function normalizeQoderMcpServers(value) {
3317
3499
  });
3318
3500
  });
3319
3501
  }
3320
- async function buildSkillFormData(name, decl, files) {
3502
+ async function buildSkillFormData(name, decl, files, fileField = "file", includeCreateFields = true, prefixTopLevelDirectory = false) {
3321
3503
  const zip = new JSZip2();
3322
3504
  for (const f of files) {
3323
- zip.file(f.relativePath, f.content);
3505
+ zip.file(prefixTopLevelDirectory ? `${name}/${f.relativePath}` : f.relativePath, f.content);
3324
3506
  }
3325
3507
  const zipContent = await zip.generateAsync({ type: "uint8array" });
3326
3508
  const formData = new FormData();
3327
3509
  formData.append(
3328
- "file",
3510
+ fileField,
3329
3511
  new File([new Uint8Array(zipContent)], `${name}.zip`, {
3330
3512
  type: "application/zip"
3331
3513
  })
3332
3514
  );
3333
- formData.append("name", name);
3334
- formData.append("type", "custom");
3335
- if (decl.description) formData.append("description", decl.description);
3515
+ if (includeCreateFields) {
3516
+ formData.append("name", name);
3517
+ formData.append("type", "custom");
3518
+ if (decl.description) formData.append("description", decl.description);
3519
+ }
3336
3520
  return formData;
3337
3521
  }
3338
3522
 
@@ -3581,7 +3765,9 @@ function mapAgent3(name, decl, refs, version, projectName, skillVersions) {
3581
3765
  }
3582
3766
  const BAILIAN_BUILTINS = /* @__PURE__ */ new Set(["bash", "read", "write", "edit", "glob", "grep", "download_file"]);
3583
3767
  if (decl.tools) {
3584
- const toolConfigs = resolveBuiltinTools(decl.tools, { supportedWireNames: BAILIAN_BUILTINS }).map((tool) => ({
3768
+ const toolConfigs = resolveBuiltinTools(decl.tools, {
3769
+ supportedWireNames: BAILIAN_BUILTINS
3770
+ }).map((tool) => ({
3585
3771
  name: tool.wireName,
3586
3772
  enabled: true
3587
3773
  }));
@@ -3655,30 +3841,60 @@ function mapSession3(bindings) {
3655
3841
  if (bindings.memory_store_ids.length) body.memory_store_ids = bindings.memory_store_ids;
3656
3842
  return body;
3657
3843
  }
3658
- function mapDeploymentToSession(decl, refs, fileIds) {
3844
+ function mapDeployment3(name, decl, refs, projectName, uploadedFiles) {
3845
+ const agent = { id: refs.agent_id };
3846
+ if (refs.agent_version !== void 0) agent.version = refs.agent_version;
3659
3847
  const body = {
3660
- agent: refs.agent_id,
3661
- environment_id: refs.environment_id
3848
+ name,
3849
+ agent,
3850
+ environment_id: refs.environment_id,
3851
+ initial_events: mapMessageEvents(decl.initial_events)
3662
3852
  };
3663
- if (decl.description) body.title = decl.description;
3664
- if (fileIds.length) {
3665
- const fileResources = (decl.resources ?? []).filter((r) => r.type === "file");
3666
- body.resources = fileIds.map((id, index) => {
3667
- const entry = { type: "file", file_id: id };
3668
- const mountPath = fileResources[index]?.mount_path;
3669
- if (mountPath) entry.mount_path = mountPath;
3670
- return entry;
3671
- });
3853
+ if (decl.description) body.description = decl.description;
3854
+ if (refs.vault_ids.length) body.vault_ids = refs.vault_ids;
3855
+ const resources = mapDeploymentResources3(decl, uploadedFiles);
3856
+ if (resources.length) body.resources = resources;
3857
+ if (decl.schedule) {
3858
+ body.schedule = {
3859
+ type: "cron",
3860
+ expression: decl.schedule.expression,
3861
+ timezone: decl.schedule.timezone
3862
+ };
3863
+ }
3864
+ if (projectName) {
3865
+ body.metadata = injectMetadata(decl.metadata, projectName, name);
3866
+ } else if (decl.metadata) {
3867
+ body.metadata = decl.metadata;
3672
3868
  }
3673
3869
  return body;
3674
3870
  }
3675
- function mapInitialEvents2(events) {
3676
- const input = events.filter((e) => e.type === "user.message" || e.type === "system.message").map((e) => ({
3871
+ function mapDeploymentUpdate3(name, decl, refs, projectName, uploadedFiles, existingMetadata) {
3872
+ const body = mapDeployment3(name, decl, refs, projectName, uploadedFiles);
3873
+ body.description = decl.description ?? "";
3874
+ body.vault_ids = refs.vault_ids;
3875
+ body.resources = mapDeploymentResources3(decl, uploadedFiles);
3876
+ if (!decl.schedule) body.schedule = null;
3877
+ if (!projectName && !decl.metadata && existingMetadata) body.metadata = existingMetadata;
3878
+ return body;
3879
+ }
3880
+ function mapDeploymentResources3(decl, uploadedFiles) {
3881
+ const resources = [];
3882
+ for (const resource of decl.resources ?? []) {
3883
+ if (resource.type !== "file") continue;
3884
+ const fileId = resource.file_id ?? (resource.source ? uploadedFiles?.get(resource.source) : void 0);
3885
+ if (!fileId) continue;
3886
+ const entry = { type: "file", file_id: fileId };
3887
+ if (resource.mount_path) entry.mount_path = resolveSandboxMountPath("bailian", resource.mount_path);
3888
+ resources.push(entry);
3889
+ }
3890
+ return resources;
3891
+ }
3892
+ function mapMessageEvents(events) {
3893
+ return events.filter((event) => event.type === "user.message" || event.type === "system.message").map((event) => ({
3677
3894
  role: "user",
3678
3895
  type: "message",
3679
- content: [{ type: "text", text: e.content }]
3896
+ content: [{ type: "text", text: event.content }]
3680
3897
  }));
3681
- return { input };
3682
3898
  }
3683
3899
  function mapSendMessage3(text) {
3684
3900
  return {
@@ -3710,7 +3926,8 @@ var BailianAdapter = class _BailianAdapter {
3710
3926
  agent: "/agents",
3711
3927
  skill: "/skills",
3712
3928
  vault: "/vaults",
3713
- file: "/files"
3929
+ file: "/files",
3930
+ deployment: "/deployments"
3714
3931
  };
3715
3932
  async findResource(type, name, id) {
3716
3933
  const accept = type === "agent" ? (r) => !isArchivedAgent(r) : void 0;
@@ -3976,7 +4193,7 @@ var BailianAdapter = class _BailianAdapter {
3976
4193
  }
3977
4194
  async waitForFileReady(fileId) {
3978
4195
  const start = Date.now();
3979
- let delay2 = FILE_SCAN_BACKOFF.initial;
4196
+ let delay3 = FILE_SCAN_BACKOFF.initial;
3980
4197
  let logged = false;
3981
4198
  while (true) {
3982
4199
  const detail = await this.client.get(`/files/${fileId}`);
@@ -3995,8 +4212,8 @@ var BailianAdapter = class _BailianAdapter {
3995
4212
  );
3996
4213
  return;
3997
4214
  }
3998
- await new Promise((r) => setTimeout(r, delay2));
3999
- delay2 = Math.min(delay2 * FILE_SCAN_BACKOFF.factor, FILE_SCAN_BACKOFF.max);
4215
+ await new Promise((r) => setTimeout(r, delay3));
4216
+ delay3 = Math.min(delay3 * FILE_SCAN_BACKOFF.factor, FILE_SCAN_BACKOFF.max);
4000
4217
  }
4001
4218
  }
4002
4219
  // --- Vault (Vaults API) ---
@@ -4055,46 +4272,93 @@ var BailianAdapter = class _BailianAdapter {
4055
4272
  async deleteCredential(vaultId, credentialId) {
4056
4273
  await this.client.delete(`/vaults/${vaultId}/credentials/${credentialId}`);
4057
4274
  }
4058
- // --- Deployment (emulated) ---
4059
- async createDeployment(_name, _decl, _refs, _basePath) {
4060
- return { id: null, type: "deployment" };
4275
+ // --- Deployment ---
4276
+ async createDeployment(name, decl, refs, basePath) {
4277
+ const uploaded = await this.uploadDeploymentFiles(decl, basePath);
4278
+ const body = mapDeployment3(name, decl, refs, this.projectName, uploaded);
4279
+ try {
4280
+ const res = await this.client.post("/deployments", body);
4281
+ return toRemoteResource(res);
4282
+ } catch (error) {
4283
+ preserveDeploymentFilesOnConflict(error, uploaded);
4284
+ }
4061
4285
  }
4062
- async updateDeployment(_id, _name, _decl, _refs, _basePath) {
4063
- return { id: null, type: "deployment" };
4286
+ async updateDeployment(id, name, decl, refs, basePath, preparedFiles) {
4287
+ if (!id) return this.createDeployment(name, decl, refs, basePath);
4288
+ const current = await this.client.get(`/deployments/${id}`);
4289
+ const uploaded = preparedFiles ? new Map(preparedFiles) : await this.uploadDeploymentFiles(decl, basePath);
4290
+ const body = mapDeploymentUpdate3(
4291
+ name,
4292
+ decl,
4293
+ refs,
4294
+ this.projectName,
4295
+ uploaded,
4296
+ current.metadata
4297
+ );
4298
+ const res = await this.client.post(`/deployments/${id}`, body);
4299
+ return toRemoteResource(res);
4064
4300
  }
4065
- async deleteDeployment(_id) {
4301
+ async deleteDeployment(id) {
4302
+ await this.client.post(`/deployments/${id}/archive`, {});
4066
4303
  }
4067
4304
  async runDeployment(ctx) {
4068
- const fileIds = [];
4069
- for (const r of ctx.decl.resources ?? []) {
4070
- if (r.type === "file") {
4071
- if (r.file_id) {
4072
- fileIds.push(r.file_id);
4073
- } else if (r.source) {
4074
- fileIds.push(await this.uploadSessionFile(r.source, ctx.basePath));
4075
- }
4076
- }
4077
- }
4078
- const body = mapDeploymentToSession(ctx.decl, ctx.refs, fileIds);
4079
- const sessionRes = await this.client.post("/sessions", body);
4080
- const sessionId = sessionRes.id;
4081
- const eventsBody = mapInitialEvents2(ctx.decl.initial_events);
4082
- const input = eventsBody.input;
4083
- if (input.length) {
4084
- await this.client.post(`/sessions/${sessionId}/events`, eventsBody);
4305
+ if (!ctx.id) {
4306
+ throw new UserError(`Deployment '${ctx.name}' has no remote id; run \`agents apply\` first.`);
4085
4307
  }
4086
- return { session_id: sessionId };
4308
+ const res = await this.client.post(`/deployments/${ctx.id}/run`, {});
4309
+ return {
4310
+ run_id: res.id,
4311
+ session_id: res.session_id ?? null,
4312
+ error: res.error ?? void 0
4313
+ };
4087
4314
  }
4088
4315
  async getDeployment(ctx) {
4089
- const plan = mapDeploymentToSession(ctx.decl, ctx.refs, []);
4316
+ if (!ctx.id) {
4317
+ throw new UserError(`Deployment '${ctx.name}' has no remote id; run \`agents apply\` first.`);
4318
+ }
4319
+ const res = await this.client.get(`/deployments/${ctx.id}`);
4320
+ return toDeploymentInfo3(res);
4321
+ }
4322
+ async listDeployments(filter) {
4323
+ const params = new URLSearchParams();
4324
+ if (filter?.agent_id) params.set("agent_id", filter.agent_id);
4325
+ if (filter?.status) params.set("status", filter.status);
4326
+ if (filter?.include_archived) params.set("include_archived", "true");
4327
+ if (filter?.limit) params.set("limit", String(filter.limit));
4328
+ if (filter?.page) params.set("page", filter.page);
4329
+ if (filter?.created_at_gte) params.set("created_at[gte]", filter.created_at_gte);
4330
+ if (filter?.created_at_lte) params.set("created_at[lte]", filter.created_at_lte);
4331
+ const query2 = params.toString();
4332
+ const res = await this.client.get(`/deployments${query2 ? `?${query2}` : ""}`);
4333
+ const nextPage = res.next_page ?? void 0;
4090
4334
  return {
4091
- id: ctx.id,
4092
- status: "emulated (local)",
4093
- schedule: ctx.decl.schedule,
4094
- attributes: { materialization_plan: plan }
4335
+ deployments: (res.data ?? []).map(toDeploymentInfo3),
4336
+ has_more: nextPage != null,
4337
+ next_page: nextPage
4095
4338
  };
4096
4339
  }
4097
- async uploadSessionFile(source, basePath) {
4340
+ async pauseDeployment(ctx) {
4341
+ return this.setDeploymentPaused(ctx, true);
4342
+ }
4343
+ async unpauseDeployment(ctx) {
4344
+ return this.setDeploymentPaused(ctx, false);
4345
+ }
4346
+ async setDeploymentPaused(ctx, paused) {
4347
+ if (!ctx.id) throw new UserError(`Deployment '${ctx.name}' has no remote id; run \`agents apply\` first.`);
4348
+ const action = paused ? "pause" : "unpause";
4349
+ const res = await this.client.post(`/deployments/${ctx.id}/${action}`, {});
4350
+ return toDeploymentInfo3(res);
4351
+ }
4352
+ async uploadDeploymentFiles(decl, basePath) {
4353
+ const uploaded = /* @__PURE__ */ new Map();
4354
+ for (const resource of decl.resources ?? []) {
4355
+ if (resource.type === "file" && !resource.file_id && resource.source && !uploaded.has(resource.source)) {
4356
+ uploaded.set(resource.source, await this.uploadDeploymentFile(resource.source, basePath));
4357
+ }
4358
+ }
4359
+ return uploaded;
4360
+ }
4361
+ async uploadDeploymentFile(source, basePath) {
4098
4362
  const fullPath = resolve3(dirname3(basePath), source);
4099
4363
  const content = readFileSync3(fullPath);
4100
4364
  const formData = new FormData();
@@ -4190,10 +4454,23 @@ var BailianAdapter = class _BailianAdapter {
4190
4454
  function isArchivedAgent(raw) {
4191
4455
  return typeof raw.archived_at === "string" && raw.archived_at.trim().length > 0;
4192
4456
  }
4193
- function toSessionInfo3(res) {
4194
- return buildSessionInfo(res, () => []);
4195
- }
4196
- function toBailianSkillInfo(res) {
4457
+ function toDeploymentInfo3(res) {
4458
+ const schedule = res.schedule;
4459
+ return {
4460
+ id: res.id ?? null,
4461
+ status: res.status ?? "unknown",
4462
+ paused_reason: res.paused_reason ?? void 0,
4463
+ schedule: schedule ? {
4464
+ expression: schedule.expression,
4465
+ timezone: schedule.timezone
4466
+ } : void 0,
4467
+ attributes: res
4468
+ };
4469
+ }
4470
+ function toSessionInfo3(res) {
4471
+ return buildSessionInfo(res, () => []);
4472
+ }
4473
+ function toBailianSkillInfo(res) {
4197
4474
  const rawSource = String(res.source ?? "").toLowerCase();
4198
4475
  const source = rawSource === "customer" ? "custom" : "official";
4199
4476
  return {
@@ -4286,9 +4563,8 @@ var BAILIAN_CAPABILITIES = {
4286
4563
  remediation: "deploy agents independently and orchestrate via MCP"
4287
4564
  },
4288
4565
  deployment: {
4289
- tier: "emulated",
4290
- reason: "no deployment primitive on Bailian; expanded into a session at run time",
4291
- remediation: "scheduling and outcome rubrics are not enforced server-side \u2014 use external cron/CI for always-on or scheduled runs"
4566
+ tier: "native",
4567
+ reason: "deployments API with cron schedules, manual runs, pause/unpause and archive"
4292
4568
  },
4293
4569
  session: { tier: "native", reason: "sessions API" },
4294
4570
  identity: { tier: "unsupported", reason: "no mapped Identity primitive on Bailian" },
@@ -4590,7 +4866,7 @@ function mapAgent4(name, decl, refs, version, projectName) {
4590
4866
  }
4591
4867
  return body;
4592
4868
  }
4593
- function mapDeploymentToSession2(decl, refs, fileIds) {
4869
+ function mapDeploymentToSession(decl, refs, fileIds) {
4594
4870
  const body = {
4595
4871
  agent: refs.agent_id,
4596
4872
  environment_id: refs.environment_id
@@ -4953,7 +5229,7 @@ var ArkAdapter = class _ArkAdapter {
4953
5229
  }
4954
5230
  }
4955
5231
  }
4956
- const body = mapDeploymentToSession2(ctx.decl, ctx.refs, fileIds);
5232
+ const body = mapDeploymentToSession(ctx.decl, ctx.refs, fileIds);
4957
5233
  const sessionRes = await this.client.post("/sessions", body);
4958
5234
  const sessionId = sessionRes.id;
4959
5235
  const events = ctx.decl.initial_events.filter((e) => e.type === "user.message" || e.type === "system.message").map((e) => ({
@@ -4966,7 +5242,7 @@ var ArkAdapter = class _ArkAdapter {
4966
5242
  return { session_id: sessionId };
4967
5243
  }
4968
5244
  async getDeployment(ctx) {
4969
- const plan = mapDeploymentToSession2(ctx.decl, ctx.refs, []);
5245
+ const plan = mapDeploymentToSession(ctx.decl, ctx.refs, []);
4970
5246
  return {
4971
5247
  id: ctx.id,
4972
5248
  status: "emulated (local)",
@@ -5239,6 +5515,9 @@ var packagesSchema = z5.object({
5239
5515
  gem: z5.array(z5.string()).optional(),
5240
5516
  go: z5.array(z5.string()).optional()
5241
5517
  });
5518
+ var setupScriptSchema = z5.string().refine((value) => new TextEncoder().encode(value).byteLength <= 64 * 1024, {
5519
+ message: "setup_script must not exceed 65536 UTF-8 bytes"
5520
+ });
5242
5521
  var environmentSchema = z5.object({
5243
5522
  name: z5.string().optional(),
5244
5523
  description: z5.string().optional(),
@@ -5248,7 +5527,8 @@ var environmentSchema = z5.object({
5248
5527
  config: z5.object({
5249
5528
  type: z5.enum(["cloud", "self_hosted"]),
5250
5529
  networking: networkingSchema.optional(),
5251
- packages: packagesSchema.optional()
5530
+ packages: packagesSchema.optional(),
5531
+ setup_script: setupScriptSchema.optional()
5252
5532
  }),
5253
5533
  metadata: z5.record(z5.string(), z5.string()).optional()
5254
5534
  });
@@ -5412,6 +5692,9 @@ var agentSkillRefSchema = z5.object({
5412
5692
  var agentDeliverySchema = z5.object({
5413
5693
  type: z5.enum(["managed", "forward"])
5414
5694
  });
5695
+ var managedToolConfigSchema = z5.object({
5696
+ enabled_tools: z5.array(z5.string().min(1))
5697
+ });
5415
5698
  var sessionGithubRepoResourceSchema = z5.object({
5416
5699
  type: z5.literal("github_repository"),
5417
5700
  url: z5.string().url(),
@@ -5433,18 +5716,27 @@ var agentSchema = z5.object({
5433
5716
  mcp_servers: z5.array(mcpServerSchema).optional(),
5434
5717
  skills: z5.array(z5.union([z5.string(), agentSkillRefSchema])).optional(),
5435
5718
  vault: z5.string().optional(),
5719
+ files: z5.array(z5.string()).optional(),
5436
5720
  memory_stores: z5.array(z5.string()).optional(),
5721
+ default_memory_store: z5.object({
5722
+ name: z5.string().trim().min(1).max(255),
5723
+ description: z5.string().max(1024).optional(),
5724
+ delete_on_destroy: z5.boolean().optional().default(false)
5725
+ }).optional(),
5437
5726
  resources: z5.array(sessionGithubRepoResourceSchema).optional(),
5438
5727
  multiagent: multiagentSchema.optional(),
5439
5728
  metadata: z5.record(z5.string(), z5.string()).optional(),
5729
+ environment_variables: z5.record(z5.string().min(1), z5.string()).optional(),
5730
+ managed_tool_config: managedToolConfigSchema.optional(),
5440
5731
  delivery: z5.record(z5.string(), agentDeliverySchema).optional()
5441
5732
  });
5442
5733
  var channelSchema = z5.object({
5443
5734
  provider: z5.string().optional(),
5444
- agent: z5.string().min(1),
5735
+ agent: z5.string().min(1).optional(),
5445
5736
  identity: z5.string().min(1).optional(),
5446
5737
  type: z5.string().min(1),
5447
5738
  name: z5.string().trim().min(1).optional(),
5739
+ mode: z5.enum(["fixed", "pairing"]).optional().default("fixed"),
5448
5740
  enabled: z5.boolean().optional(),
5449
5741
  credentials: z5.record(z5.string(), coerceString).optional(),
5450
5742
  options: z5.record(z5.string(), z5.unknown()).optional()
@@ -5683,14 +5975,29 @@ async function computeResourceHash(address, config, basePath, state) {
5683
5975
  if (!decl) return "";
5684
5976
  if (address.type === "skill") {
5685
5977
  const skillDecl = decl;
5978
+ const apiMode = resolveQoderApiMode(address.type, address.name, address.provider, config);
5686
5979
  if (basePath) {
5687
5980
  const fileHash = computeSkillContentHash(skillDecl.source, basePath);
5688
- return contentHash({ decl, fileHash });
5981
+ return contentHash({ decl, fileHash, apiMode });
5689
5982
  }
5983
+ return contentHash({ decl, apiMode });
5984
+ }
5985
+ if (address.type === "environment" || address.type === "vault" || address.type === "memory_store") {
5986
+ return contentHash({ decl, apiMode: resolveQoderApiMode(address.type, address.name, address.provider, config) });
5987
+ }
5988
+ if (address.type === "file" && basePath) {
5989
+ const fileDecl = decl;
5990
+ const fileHash = computeLocalFileContentHash(fileDecl.source, basePath);
5991
+ return contentHash({
5992
+ decl,
5993
+ fileHash,
5994
+ apiMode: resolveQoderApiMode("file", address.name, address.provider, config)
5995
+ });
5690
5996
  }
5691
5997
  if (address.type === "deployment") {
5692
5998
  const refs = resolveDeploymentReferenceIds(decl, config, address.provider, state);
5693
- if (refs) return contentHash({ decl, refs });
5999
+ const sourceHashes = basePath ? computeDeploymentSourceHashes(decl, basePath) : void 0;
6000
+ if (refs || sourceHashes) return contentHash({ decl, refs, sourceHashes });
5694
6001
  }
5695
6002
  if (address.type === "template") {
5696
6003
  const refs = resolveTemplateReferenceIds(decl, config, address.provider, state);
@@ -5707,7 +6014,28 @@ async function computeResourceHash(address, config, basePath, state) {
5707
6014
  }
5708
6015
  return contentHash(decl);
5709
6016
  }
6017
+ function resolveQoderApiMode(type, name, provider, config) {
6018
+ if (provider !== "qoder") return void 0;
6019
+ if (type === "environment" && config.environments?.[name]?.environment_id) return "auto";
6020
+ for (const agent of Object.values(config.agents ?? {})) {
6021
+ if (agent.provider && agent.provider !== provider) continue;
6022
+ const referenced = type === "environment" ? agent.environment === name : type === "skill" ? agent.skills?.some(
6023
+ (skill) => typeof skill === "string" ? skill === name : skill.type === "custom" && skill.skill_id === name
6024
+ ) : type === "vault" ? agent.vault === name : type === "memory_store" ? agent.memory_stores?.includes(name) : agent.files?.includes(name);
6025
+ if (referenced && agent.delivery?.qoder?.type === "forward") return "forward";
6026
+ }
6027
+ return "managed";
6028
+ }
6029
+ function computeReplacementFingerprint(address, config) {
6030
+ if (address.type !== "channel") return void 0;
6031
+ const decl = config.channels?.[address.name];
6032
+ if (!decl) return void 0;
6033
+ return contentHash({ channel_type: decl.type, mode: decl.mode ?? "fixed", credentials: decl.credentials ?? {} });
6034
+ }
5710
6035
  function resolveChannelReferenceIds(decl, config, provider, state) {
6036
+ if (decl.mode === "pairing" || !decl.agent) {
6037
+ return { mode: "pairing" };
6038
+ }
5711
6039
  const agent = config.agents?.[decl.agent];
5712
6040
  const agentType = agent?.delivery?.[provider]?.type === "forward" ? "template" : "agent";
5713
6041
  const identity = decl.identity ?? config.defaults?.identity;
@@ -5730,7 +6058,14 @@ function resolveTemplateReferenceIds(decl, config, provider, state) {
5730
6058
  environment_id: environment?.environment_id ?? (decl.environment ? state?.getResource({ type: "environment", name: decl.environment, provider })?.remote_id ?? void 0 : void 0),
5731
6059
  tunnel_id: tunnel?.tunnel_id,
5732
6060
  vault_ids: decl.vault ? [state?.getResource({ type: "vault", name: decl.vault, provider })?.remote_id ?? decl.vault] : [],
5733
- skill_ids: skillIds
6061
+ skill_ids: skillIds,
6062
+ memory_store_ids: (decl.memory_stores ?? []).map(
6063
+ (memoryStore) => state?.getResource({ type: "memory_store", name: memoryStore, provider })?.remote_id ?? memoryStore
6064
+ ),
6065
+ file_ids: (decl.files ?? []).map(
6066
+ (file) => state?.getResource({ type: "file", name: file, provider })?.remote_id ?? file
6067
+ ),
6068
+ identity_id: decl.memory_stores?.length && config.defaults?.identity ? state?.getResource({ type: "identity", name: config.defaults.identity, provider })?.remote_id : void 0
5734
6069
  };
5735
6070
  }
5736
6071
  function resolveDeploymentReferenceIds(decl, config, provider, state) {
@@ -5745,6 +6080,23 @@ function resolveDeploymentReferenceIds(decl, config, provider, state) {
5745
6080
  function getDeclaration(address, config) {
5746
6081
  return getResourceDeclaration(address, config);
5747
6082
  }
6083
+ function computeDeploymentSourceHashes(decl, basePath) {
6084
+ const sources = [
6085
+ ...new Set(
6086
+ (decl.resources ?? []).flatMap(
6087
+ (resource) => resource.type === "file" && !resource.file_id && resource.source ? [resource.source] : []
6088
+ )
6089
+ )
6090
+ ];
6091
+ if (sources.length === 0) return void 0;
6092
+ return Object.fromEntries(sources.map((source) => [source, computeLocalFileContentHash(source, basePath)]));
6093
+ }
6094
+ function computeLocalFileContentHash(source, basePath) {
6095
+ const fullPath = resolve9(dirname8(basePath), source);
6096
+ const stat = statSync2(fullPath, { throwIfNoEntry: false });
6097
+ if (!stat?.isFile()) return "";
6098
+ return contentHash(readFileSync6(fullPath).toString("base64"));
6099
+ }
5748
6100
  function computeSkillContentHash(source, basePath) {
5749
6101
  const fullPath = resolve9(dirname8(basePath), source);
5750
6102
  const stat = statSync2(fullPath, { throwIfNoEntry: false });
@@ -5893,11 +6245,21 @@ function resolveTemplateRefs(agentName, config, provider, state) {
5893
6245
  const environment = config.environments?.[agent.environment];
5894
6246
  if (!environment) throw new UserError(`Environment '${agent.environment}' is not defined in config.`);
5895
6247
  const agentRefs = resolveAgentRefs(agentName, config, provider, state);
6248
+ const memoryStoreIds = (agent.memory_stores ?? []).map(
6249
+ (memoryStore) => requireRef(state, { type: "memory_store", name: memoryStore, provider })
6250
+ );
6251
+ const identityName = config.defaults?.identity;
5896
6252
  return {
5897
6253
  ...agentRefs,
5898
6254
  environment_id: environment.environment_id ?? requireRef(state, { type: "environment", name: agent.environment, provider }),
5899
6255
  ...agent.tunnel ? { tunnel_id: resolveTunnelIdFromConfig(config, agent.tunnel, provider) } : {},
5900
- vault_ids: agent.vault ? [requireRef(state, { type: "vault", name: agent.vault, provider })] : []
6256
+ vault_ids: agent.vault ? [requireRef(state, { type: "vault", name: agent.vault, provider })] : [],
6257
+ file_ids: (agent.files ?? []).map((file) => requireRef(state, { type: "file", name: file, provider })),
6258
+ memory_store_ids: memoryStoreIds,
6259
+ owned_memory_store_ids: state.listResources().filter(
6260
+ (resource) => resource.address.provider === provider && resource.address.type === "memory_store" && resource.api_mode === "forward" && typeof resource.remote_id === "string"
6261
+ ).map((resource) => resource.remote_id),
6262
+ ...identityName ? { identity_id: requireRef(state, { type: "identity", name: identityName, provider }) } : {}
5901
6263
  };
5902
6264
  }
5903
6265
  function resolveDeploymentRefs(deploymentName, config, provider, state) {
@@ -5959,6 +6321,12 @@ function resolveDeploymentRefs(deploymentName, config, provider, state) {
5959
6321
  function resolveChannelRefs(channelName, config, provider, state) {
5960
6322
  const channel = config.channels?.[channelName];
5961
6323
  if (!channel) throw new UserError(`Channel '${channelName}' not found in config`);
6324
+ if (channel.mode === "pairing") {
6325
+ return {};
6326
+ }
6327
+ if (!channel.agent) {
6328
+ throw new UserError(`Channel '${channelName}' is fixed mode and must declare agent`);
6329
+ }
5962
6330
  const agent = config.agents?.[channel.agent];
5963
6331
  if (!agent) throw new UserError(`Channel '${channelName}' references unknown agent '${channel.agent}'`);
5964
6332
  const agentType = agent.delivery?.[provider]?.type === "forward" ? "template" : "agent";
@@ -6081,6 +6449,7 @@ async function executePlan(plan, ctx, options = {}) {
6081
6449
  await runWithConcurrency(level, concurrency, runAction);
6082
6450
  await ctx.state.save();
6083
6451
  }
6452
+ await reconcileDefaultMemoryStores(ctx, new Set(plan.actions.map((action) => action.address.provider)));
6084
6453
  for (const action of deletions) {
6085
6454
  await runAction(action);
6086
6455
  await ctx.state.save();
@@ -6091,6 +6460,41 @@ async function executePlan(plan, ctx, options = {}) {
6091
6460
  partial: results.some((r) => r.status === "failed")
6092
6461
  };
6093
6462
  }
6463
+ async function reconcileDefaultMemoryStores(ctx, plannedProviders) {
6464
+ const identityName = ctx.config.defaults?.identity;
6465
+ for (const [agentName, agent] of Object.entries(ctx.config.agents ?? {})) {
6466
+ const desired = agent.default_memory_store;
6467
+ if (!desired || agent.delivery?.qoder?.type !== "forward") continue;
6468
+ const providerName = "qoder";
6469
+ if (agent.provider && agent.provider !== providerName || !plannedProviders.has(providerName)) continue;
6470
+ const provider = ctx.providers.get(providerName);
6471
+ if (!provider?.reconcileDefaultMemoryStore || !identityName) continue;
6472
+ const identityId = ctx.state.getResource({
6473
+ type: "identity",
6474
+ name: identityName,
6475
+ provider: providerName
6476
+ })?.remote_id;
6477
+ const templateId = ctx.state.getResource({ type: "template", name: agentName, provider: providerName })?.remote_id;
6478
+ if (!identityId || !templateId) continue;
6479
+ const result = await provider.reconcileDefaultMemoryStore(identityId, templateId, desired);
6480
+ const resource = { type: "template", name: agentName, provider: providerName };
6481
+ if (result.status === "pending") {
6482
+ emitRuntimeFeedback(ctx.onFeedback, {
6483
+ type: "provider_wait",
6484
+ level: "warning",
6485
+ resource,
6486
+ message: `default memory store for template.${agentName} is pending \u2014 create the first Forward Session, then run apply again`
6487
+ });
6488
+ } else if (result.status === "updated") {
6489
+ emitRuntimeFeedback(ctx.onFeedback, {
6490
+ type: "resource_action_success",
6491
+ level: "success",
6492
+ resource,
6493
+ message: `updated default memory store for template.${agentName} to "${desired.name}"`
6494
+ });
6495
+ }
6496
+ }
6497
+ }
6094
6498
  async function runWithConcurrency(items, limit, worker) {
6095
6499
  if (items.length === 0) return;
6096
6500
  let cursor = 0;
@@ -6157,8 +6561,8 @@ async function executeAction(action, provider, ctx) {
6157
6561
  resource: action.address,
6158
6562
  message: `update ${action.address.type}.${action.address.name} (${action.address.provider}) \u2014 not found remotely, recreating`
6159
6563
  });
6160
- ctx.state.removeResource(action.address);
6161
- return executeActionInner({ ...action, action: "create" }, provider, ctx);
6564
+ ctx.state.removeResource(action.previousAddress ?? action.address);
6565
+ return executeActionInner({ ...action, action: "create", previousAddress: void 0 }, provider, ctx);
6162
6566
  }
6163
6567
  }
6164
6568
  async function executeActionInner(action, provider, ctx) {
@@ -6169,6 +6573,7 @@ async function executeActionInner(action, provider, ctx) {
6169
6573
  const existing = ctx.state.getResource(address);
6170
6574
  if (!existing) return false;
6171
6575
  const id = existing.remote_id;
6576
+ const apiMode2 = existing.api_mode;
6172
6577
  if (type === "environment" || type === "identity") {
6173
6578
  const externalReference = type === "environment" ? ctx.config.environments?.[name]?.environment_id : ctx.config.identities?.[name]?.identity_id;
6174
6579
  if (existing.externally_managed || externalReference) {
@@ -6180,13 +6585,13 @@ async function executeActionInner(action, provider, ctx) {
6180
6585
  try {
6181
6586
  switch (type) {
6182
6587
  case "environment":
6183
- await provider.deleteEnvironment(id);
6588
+ await provider.deleteEnvironment(id, false, apiMode2);
6184
6589
  break;
6185
6590
  case "vault":
6186
- await provider.deleteVault(id);
6591
+ await provider.deleteVault(id, apiMode2);
6187
6592
  break;
6188
6593
  case "skill":
6189
- await provider.deleteSkill(id);
6594
+ await provider.deleteSkill(id, apiMode2);
6190
6595
  break;
6191
6596
  case "agent":
6192
6597
  await provider.deleteAgent(id);
@@ -6194,17 +6599,17 @@ async function executeActionInner(action, provider, ctx) {
6194
6599
  case "template":
6195
6600
  if (!provider.archiveTemplate)
6196
6601
  throw new UserError(`Provider '${address.provider}' does not support templates`);
6197
- await provider.archiveTemplate(id);
6602
+ await provider.archiveTemplate(id, ownedForwardMemoryStoreIds(ctx, address.provider));
6198
6603
  break;
6199
6604
  case "memory_store":
6200
6605
  if (!provider.deleteMemoryStore) throw memoryStoreUnsupported(address.provider);
6201
- await provider.deleteMemoryStore(id);
6606
+ await provider.deleteMemoryStore(id, apiMode2);
6202
6607
  break;
6203
6608
  case "deployment":
6204
6609
  await provider.deleteDeployment(id);
6205
6610
  break;
6206
6611
  case "file":
6207
- await provider.deleteFile(id);
6612
+ await provider.deleteFile(id, apiMode2);
6208
6613
  break;
6209
6614
  case "identity":
6210
6615
  if (!provider.deleteIdentity)
@@ -6231,7 +6636,11 @@ async function executeActionInner(action, provider, ctx) {
6231
6636
  return false;
6232
6637
  }
6233
6638
  const isUpdate = action.action === "update";
6234
- const existingId = isUpdate ? ctx.state.getResource(address)?.remote_id : void 0;
6639
+ const priorAddress = action.previousAddress ?? address;
6640
+ const existingId = isUpdate ? ctx.state.getResource(priorAddress)?.remote_id : void 0;
6641
+ const apiMode = resolveResourceApiMode(type, name, address.provider, ctx.config);
6642
+ const priorApiMode = ctx.state.getResource(priorAddress)?.api_mode ?? (address.provider === "qoder" ? "managed" : void 0);
6643
+ const apiModeChanged = isUpdate && apiMode !== void 0 && priorApiMode !== apiMode;
6235
6644
  let result;
6236
6645
  switch (type) {
6237
6646
  case "environment": {
@@ -6246,6 +6655,17 @@ async function executeActionInner(action, provider, ctx) {
6246
6655
  resource: action.address,
6247
6656
  message: `${action.action} ${action.address.type}.${action.address.name} (${action.address.provider}) \u2014 external reference, no remote mutation`
6248
6657
  });
6658
+ } else if (apiModeChanged) {
6659
+ try {
6660
+ result = await provider.createEnvironment(remoteName, decl, apiMode);
6661
+ } catch (err) {
6662
+ result = await adoptOnConflict(err, address, provider, ctx.onFeedback, {
6663
+ mode: apiMode,
6664
+ onExisting: (existing) => provider.updateEnvironment(existing.id, remoteName, decl, apiMode)
6665
+ });
6666
+ adopted = true;
6667
+ }
6668
+ if (existingId) await provider.deleteEnvironment(existingId, false, priorApiMode);
6249
6669
  } else if (isUpdate) {
6250
6670
  const prior = ctx.state.getResource(address);
6251
6671
  if (prior?.externally_managed) {
@@ -6253,13 +6673,14 @@ async function executeActionInner(action, provider, ctx) {
6253
6673
  `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).`
6254
6674
  );
6255
6675
  }
6256
- result = await provider.updateEnvironment(existingId, remoteName, decl);
6676
+ result = await provider.updateEnvironment(existingId, remoteName, decl, apiMode);
6257
6677
  } else {
6258
6678
  try {
6259
- result = await provider.createEnvironment(remoteName, decl);
6679
+ result = await provider.createEnvironment(remoteName, decl, apiMode);
6260
6680
  } catch (err) {
6261
6681
  result = await adoptOnConflict(err, address, provider, ctx.onFeedback, {
6262
- onExisting: (existing) => provider.updateEnvironment(existing.id, remoteName, decl)
6682
+ mode: apiMode,
6683
+ onExisting: (existing) => provider.updateEnvironment(existing.id, remoteName, decl, apiMode)
6263
6684
  });
6264
6685
  adopted = true;
6265
6686
  }
@@ -6268,22 +6689,26 @@ async function executeActionInner(action, provider, ctx) {
6268
6689
  }
6269
6690
  case "vault": {
6270
6691
  const decl = ctx.config.vaults[name];
6271
- if (isUpdate) {
6692
+ if (apiModeChanged) {
6693
+ result = await provider.createVault(name, decl, apiMode);
6694
+ if (existingId) await provider.deleteVault(existingId, priorApiMode).catch(() => void 0);
6695
+ } else if (isUpdate) {
6272
6696
  try {
6273
- result = await provider.createVault(name, decl);
6274
- await provider.deleteVault(existingId);
6697
+ result = await provider.createVault(name, decl, apiMode);
6698
+ await provider.deleteVault(existingId, apiMode);
6275
6699
  } catch {
6276
- await provider.deleteVault(existingId);
6277
- result = await provider.createVault(name, decl);
6700
+ await provider.deleteVault(existingId, apiMode);
6701
+ result = await provider.createVault(name, decl, apiMode);
6278
6702
  }
6279
6703
  } else {
6280
6704
  try {
6281
- result = await provider.createVault(name, decl);
6705
+ result = await provider.createVault(name, decl, apiMode);
6282
6706
  } catch (err) {
6283
6707
  result = await adoptOnConflict(err, address, provider, ctx.onFeedback, {
6708
+ mode: apiMode,
6284
6709
  onExisting: async (existing) => {
6285
- await provider.deleteVault(existing.id);
6286
- return provider.createVault(name, decl);
6710
+ await provider.deleteVault(existing.id, apiMode);
6711
+ return provider.createVault(name, decl, apiMode);
6287
6712
  }
6288
6713
  });
6289
6714
  adopted = true;
@@ -6307,12 +6732,15 @@ async function executeActionInner(action, provider, ctx) {
6307
6732
  }
6308
6733
  const remoteName = decl.name ?? name;
6309
6734
  const files = await resolveSkillFiles(decl, ctx);
6310
- if (isUpdate) {
6311
- result = await provider.updateSkill(existingId, remoteName, decl, files);
6735
+ if (apiModeChanged) {
6736
+ result = await provider.createSkill(remoteName, decl, files, apiMode);
6737
+ if (existingId) await provider.deleteSkill(existingId, priorApiMode).catch(() => void 0);
6738
+ } else if (isUpdate) {
6739
+ result = await provider.updateSkill(existingId, remoteName, decl, files, apiMode);
6312
6740
  } else {
6313
6741
  const manifestName = skillNameFromFiles(files);
6314
6742
  const searchNames = manifestName && manifestName !== remoteName ? [remoteName, manifestName] : [remoteName];
6315
- const existing = await findExistingByNames(provider, "skill", searchNames);
6743
+ const existing = await findExistingByNames(provider, "skill", searchNames, apiMode);
6316
6744
  if (existing) {
6317
6745
  result = existing.resource;
6318
6746
  emitRuntimeFeedback(ctx.onFeedback, {
@@ -6324,9 +6752,10 @@ async function executeActionInner(action, provider, ctx) {
6324
6752
  adopted = true;
6325
6753
  } else {
6326
6754
  try {
6327
- result = await provider.createSkill(remoteName, decl, files);
6755
+ result = await provider.createSkill(remoteName, decl, files, apiMode);
6328
6756
  } catch (err) {
6329
6757
  result = await adoptOnConflict(err, address, provider, ctx.onFeedback, {
6758
+ mode: apiMode,
6330
6759
  searchNames,
6331
6760
  onExisting: async (existing2) => existing2
6332
6761
  });
@@ -6345,15 +6774,19 @@ async function executeActionInner(action, provider, ctx) {
6345
6774
  throw memoryStoreUnsupported(address.provider);
6346
6775
  }
6347
6776
  const reconcile = async (storeId) => {
6348
- const store = await provider.updateMemoryStore(storeId, {
6349
- name,
6350
- description: decl.description,
6351
- metadata: decl.metadata ?? {}
6352
- });
6777
+ const store = await provider.updateMemoryStore(
6778
+ storeId,
6779
+ {
6780
+ name,
6781
+ description: decl.description,
6782
+ metadata: decl.metadata ?? {}
6783
+ },
6784
+ apiMode
6785
+ );
6353
6786
  const current = /* @__PURE__ */ new Map();
6354
6787
  let cursor;
6355
6788
  do {
6356
- const page2 = await provider.listMemories(storeId, { limit: 100, cursor, view: "basic" });
6789
+ const page2 = await provider.listMemories(storeId, { limit: 100, cursor, view: "basic" }, apiMode);
6357
6790
  for (const memory of page2.data) {
6358
6791
  if (memory.type === "memory") current.set(memory.path, memory);
6359
6792
  }
@@ -6363,24 +6796,33 @@ async function executeActionInner(action, provider, ctx) {
6363
6796
  const existing = current.get(entry.key.replace(/^\/+/, ""));
6364
6797
  if (existing) {
6365
6798
  if (existing.content_sha256 !== sha256(entry.content)) {
6366
- await provider.updateMemory(storeId, existing.id, {
6367
- content: entry.content,
6368
- expected_content_sha256: existing.content_sha256
6369
- });
6799
+ await provider.updateMemory(
6800
+ storeId,
6801
+ existing.id,
6802
+ {
6803
+ content: entry.content,
6804
+ expected_content_sha256: existing.content_sha256
6805
+ },
6806
+ apiMode
6807
+ );
6370
6808
  }
6371
6809
  } else {
6372
- await provider.createMemory(storeId, { path: entry.key, content: entry.content });
6810
+ await provider.createMemory(storeId, { path: entry.key, content: entry.content }, apiMode);
6373
6811
  }
6374
6812
  }
6375
6813
  return store;
6376
6814
  };
6377
- if (isUpdate) {
6815
+ if (apiModeChanged) {
6816
+ result = await createMemoryStore2(name, decl, apiMode);
6817
+ if (existingId) await deleteMemoryStore2(existingId, priorApiMode).catch(() => void 0);
6818
+ } else if (isUpdate) {
6378
6819
  result = await reconcile(existingId);
6379
6820
  } else {
6380
6821
  try {
6381
- result = await createMemoryStore2(name, decl);
6822
+ result = await createMemoryStore2(name, decl, apiMode);
6382
6823
  } catch (err) {
6383
6824
  result = await adoptOnConflict(err, address, provider, ctx.onFeedback, {
6825
+ mode: apiMode,
6384
6826
  onExisting: async (existing) => reconcile(existing.id)
6385
6827
  });
6386
6828
  adopted = true;
@@ -6489,16 +6931,36 @@ async function executeActionInner(action, provider, ctx) {
6489
6931
  case "deployment": {
6490
6932
  const decl = ctx.config.deployments[name];
6491
6933
  const refs = resolveDeploymentRefs(name, ctx.config, address.provider, ctx.state);
6492
- if (isUpdate) {
6493
- result = await provider.updateDeployment(existingId, name, decl, refs, ctx.configPath ?? "");
6494
- } else {
6934
+ const hasLocalFileSources = decl.resources?.some(
6935
+ (resource) => resource.type === "file" && !resource.file_id && Boolean(resource.source)
6936
+ );
6937
+ const materializeDeployment = async () => {
6495
6938
  try {
6496
- result = await provider.createDeployment(name, decl, refs, ctx.configPath ?? "");
6939
+ return await provider.createDeployment(name, decl, refs, ctx.configPath ?? "");
6497
6940
  } catch (err) {
6498
- result = await adoptOnConflict(err, address, provider, ctx.onFeedback, {
6499
- onExisting: (existing) => provider.updateDeployment(existing.id, name, decl, refs, ctx.configPath ?? "")
6941
+ const preparedFiles = err instanceof DeploymentCreateConflictError ? err.preparedFiles : void 0;
6942
+ const existing = await adoptOnConflict(err, address, provider, ctx.onFeedback, {
6943
+ onExisting: (existing2) => provider.updateDeployment(existing2.id, name, decl, refs, ctx.configPath ?? "", preparedFiles)
6500
6944
  });
6501
6945
  adopted = true;
6946
+ return existing;
6947
+ }
6948
+ };
6949
+ if (isUpdate && existingId) {
6950
+ result = await provider.updateDeployment(existingId, name, decl, refs, ctx.configPath ?? "");
6951
+ } else {
6952
+ const existing = hasLocalFileSources ? await findExistingByNames(provider, "deployment", [name]) : null;
6953
+ if (existing) {
6954
+ result = await provider.updateDeployment(existing.resource.id, name, decl, refs, ctx.configPath ?? "");
6955
+ emitRuntimeFeedback(ctx.onFeedback, {
6956
+ type: "resource_adopted",
6957
+ level: "info",
6958
+ resource: address,
6959
+ message: `adopt deployment.${name} (${address.provider}) \u2014 already existed remotely as "${existing.name}"`
6960
+ });
6961
+ adopted = true;
6962
+ } else {
6963
+ result = await materializeDeployment();
6502
6964
  }
6503
6965
  }
6504
6966
  break;
@@ -6510,15 +6972,12 @@ async function executeActionInner(action, provider, ctx) {
6510
6972
  const oldId = ctx.state.getResource(address)?.remote_id;
6511
6973
  if (oldId) {
6512
6974
  try {
6513
- await provider.deleteFile(oldId);
6975
+ await provider.deleteFile(oldId, priorApiMode);
6514
6976
  } catch {
6515
6977
  }
6516
6978
  }
6517
6979
  }
6518
- const info = await provider.uploadFile(filePath, {
6519
- name: decl.name,
6520
- purpose: decl.purpose
6521
- });
6980
+ const info = await provider.uploadFile(filePath, { name: decl.name, purpose: decl.purpose }, apiMode);
6522
6981
  result = { id: info.id, type: "file" };
6523
6982
  break;
6524
6983
  }
@@ -6534,11 +6993,12 @@ async function executeActionInner(action, provider, ctx) {
6534
6993
  remoteHash = contentHash(remote.comparable);
6535
6994
  remoteSnapshot = remote.snapshot ?? remote.comparable;
6536
6995
  }
6537
- const priorResource = ctx.state.getResource(address);
6996
+ const priorResource = ctx.state.getResource(priorAddress);
6538
6997
  ctx.state.setResource({
6539
6998
  address,
6540
6999
  remote_id: result.id,
6541
7000
  externally_managed: priorResource?.externally_managed || type === "environment" && ctx.config.environments?.[name]?.environment_id || type === "identity" && ctx.config.identities?.[name]?.identity_id ? true : void 0,
7001
+ api_mode: apiMode,
6542
7002
  version: result.version,
6543
7003
  content_hash: hash,
6544
7004
  desired_hash: hash,
@@ -6546,14 +7006,44 @@ async function executeActionInner(action, provider, ctx) {
6546
7006
  desired_readiness_baseline: buildReadinessBaseline(getResourceDeclaration(address, ctx.config)),
6547
7007
  remote_hash: remoteHash,
6548
7008
  remote_snapshot: remoteSnapshot,
7009
+ replacement_fingerprint: computeReplacementFingerprint(address, ctx.config),
6549
7010
  drift_paths: [],
6550
7011
  drift_status: remoteHash ? "in_sync" : void 0
6551
7012
  });
7013
+ if (action.previousAddress) ctx.state.removeResource(action.previousAddress);
6552
7014
  return adopted;
6553
7015
  }
6554
- async function findExistingByNames(provider, type, names) {
7016
+ function ownedForwardMemoryStoreIds(ctx, provider) {
7017
+ return ctx.state.listResources().filter(
7018
+ (resource) => resource.address.provider === provider && resource.address.type === "memory_store" && resource.api_mode === "forward" && typeof resource.remote_id === "string"
7019
+ ).map((resource) => resource.remote_id);
7020
+ }
7021
+ function resolveResourceApiMode(type, name, provider, config) {
7022
+ if (provider !== "qoder" || type !== "environment" && type !== "skill" && type !== "vault" && type !== "memory_store" && type !== "file") {
7023
+ return void 0;
7024
+ }
7025
+ if (type === "environment" && config.environments?.[name]?.environment_id) return "auto";
7026
+ let managed = false;
7027
+ let forward = false;
7028
+ for (const agent of Object.values(config.agents ?? {})) {
7029
+ if (agent.provider && agent.provider !== provider) continue;
7030
+ const referenced = type === "environment" ? agent.environment === name : type === "skill" ? agent.skills?.some(
7031
+ (skill) => typeof skill === "string" ? skill === name : skill.type === "custom" && skill.skill_id === name
7032
+ ) : type === "vault" ? agent.vault === name : type === "memory_store" ? agent.memory_stores?.includes(name) : agent.files?.includes(name);
7033
+ if (!referenced) continue;
7034
+ if (agent.delivery?.qoder?.type === "forward") forward = true;
7035
+ else managed = true;
7036
+ }
7037
+ if (managed && forward) {
7038
+ throw new UserError(
7039
+ `Qoder ${type}.${name} is referenced by both Managed and Forward agents; declare separate resources for each API domain.`
7040
+ );
7041
+ }
7042
+ return forward ? "forward" : "managed";
7043
+ }
7044
+ async function findExistingByNames(provider, type, names, mode) {
6555
7045
  for (const candidate of names) {
6556
- const found = await provider.findResource(type, candidate);
7046
+ const found = await provider.findResource(type, candidate, void 0, mode);
6557
7047
  if (found && found.id !== null) return { resource: found, name: candidate };
6558
7048
  }
6559
7049
  return null;
@@ -6561,7 +7051,7 @@ async function findExistingByNames(provider, type, names) {
6561
7051
  async function adoptOnConflict(err, address, provider, onFeedback, opts) {
6562
7052
  if (!(err instanceof ConflictError)) throw err;
6563
7053
  const candidates = opts.searchNames?.length ? opts.searchNames : [address.name];
6564
- const existing = await findExistingByNames(provider, address.type, candidates);
7054
+ const existing = await findExistingByNames(provider, address.type, candidates, opts.mode);
6565
7055
  if (!existing) throw nameReservedError(err, address, candidates.join('" / "'));
6566
7056
  emitRuntimeFeedback(onFeedback, {
6567
7057
  type: "resource_adopted",
@@ -6573,6 +7063,11 @@ async function adoptOnConflict(err, address, provider, onFeedback, opts) {
6573
7063
  }
6574
7064
  function nameReservedError(err, address, searchName) {
6575
7065
  const detail = err instanceof ApiError ? err.message : String(err);
7066
+ if (address.type === "channel" && err instanceof ApiError && err.responseBody.includes("CHANNEL_CREDENTIAL_CONFLICT")) {
7067
+ return new UserError(
7068
+ `${address.provider} rejected channel.${address.name} because its credentials are already used by another Channel. Keep the existing Channel address so it can be updated in place, remove the old Channel first, or use a different credential set. (${detail})`
7069
+ );
7070
+ }
6576
7071
  return new UserError(
6577
7072
  `${address.provider} reported ${address.type} "${searchName}" already exists, but it could not be found remotely to adopt. This usually means it was recently deleted and the provider still reserves the name. Wait for the provider to release the name, or use a different name for ${address.type}.${address.name}. (${detail})`
6578
7073
  );
@@ -6645,6 +7140,13 @@ function agentTargetsProvider(config, agentProvider, providerName) {
6645
7140
  return Object.hasOwn(config.providers, providerName);
6646
7141
  }
6647
7142
 
7143
+ // src/internal/core/agent-materialization.ts
7144
+ function resolveAgentMaterialization(provider, agent) {
7145
+ const mode = agent.delivery?.[provider]?.type ?? "managed";
7146
+ if (mode === "managed") return { resourceType: "agent", mode };
7147
+ return { resourceType: "template", mode };
7148
+ }
7149
+
6648
7150
  // src/internal/core/validate-config.ts
6649
7151
  function validateProjectConfig(config, options = {}) {
6650
7152
  const collector = new DiagnosticCollector();
@@ -6671,6 +7173,7 @@ function collectReferenceDiagnostics(config, diagnostics) {
6671
7173
  const skillNames = new Set(Object.keys(config.skills ?? {}));
6672
7174
  const vaultNames = new Set(Object.keys(config.vaults ?? {}));
6673
7175
  const memoryNames = new Set(Object.keys(config.memory_stores ?? {}));
7176
+ const fileNames = new Set(Object.keys(config.files ?? {}));
6674
7177
  const agentNames = new Set(Object.keys(config.agents ?? {}));
6675
7178
  const identityNames = new Set(Object.keys(config.identities ?? {}));
6676
7179
  if (config.defaults?.identity && !identityNames.has(config.defaults.identity)) {
@@ -6698,6 +7201,11 @@ function collectReferenceDiagnostics(config, diagnostics) {
6698
7201
  if (agent.vault && !vaultNames.has(agent.vault)) {
6699
7202
  diagnostics.error("config.agent.vault.unknown", `agent.${name}: references unknown vault '${agent.vault}'`);
6700
7203
  }
7204
+ for (const file of agent.files ?? []) {
7205
+ if (!fileNames.has(file)) {
7206
+ diagnostics.error("config.agent.file.unknown", `agent.${name}: references unknown file '${file}'`);
7207
+ }
7208
+ }
6701
7209
  for (const memory of agent.memory_stores ?? []) {
6702
7210
  if (!memoryNames.has(memory)) {
6703
7211
  diagnostics.error(
@@ -6729,7 +7237,10 @@ function collectReferenceDiagnostics(config, diagnostics) {
6729
7237
  }
6730
7238
  }
6731
7239
  for (const [name, channel] of Object.entries(config.channels ?? {})) {
6732
- if (!agentNames.has(channel.agent)) {
7240
+ if (channel.mode === "pairing") continue;
7241
+ if (!channel.agent) {
7242
+ diagnostics.error("config.channel.agent.required", `channel.${name}: fixed-mode channels require agent`);
7243
+ } else if (!agentNames.has(channel.agent)) {
6733
7244
  diagnostics.error("config.channel.agent.unknown", `channel.${name}: references unknown agent '${channel.agent}'`);
6734
7245
  }
6735
7246
  const identity = channel.identity ?? config.defaults?.identity;
@@ -6757,6 +7268,37 @@ function collectProviderCapabilities(config, providers, diagnostics) {
6757
7268
  continue;
6758
7269
  }
6759
7270
  const caps = def.capabilities;
7271
+ for (const [name, environment] of Object.entries(config.environments ?? {})) {
7272
+ if (environment.provider && environment.provider !== providerName) continue;
7273
+ if (environment.environment_id) continue;
7274
+ const address = { type: "environment", name, provider: providerName };
7275
+ if (environment.config.setup_script !== void 0 && providerName !== "qoder") {
7276
+ diagnostics.error(
7277
+ `${providerName}.environment.setup_script.unsupported`,
7278
+ `environment.${name}: provider '${providerName}' does not support setup_script; remove it or pin this environment to qoder.`,
7279
+ address
7280
+ );
7281
+ }
7282
+ if (providerName === "qoder") {
7283
+ if (environment.config.type === "self_hosted" && (environment.config.networking !== void 0 || environment.config.packages !== void 0)) {
7284
+ diagnostics.error(
7285
+ "qoder.environment.self_hosted.config.unsupported",
7286
+ `environment.${name}: Qoder self_hosted environments accept only config.type and config.setup_script; remove networking and packages.`,
7287
+ address
7288
+ );
7289
+ }
7290
+ const unsupported = ["cargo", "gem", "go"].filter(
7291
+ (key) => (environment.config.packages?.[key]?.length ?? 0) > 0
7292
+ );
7293
+ if (unsupported.length > 0) {
7294
+ diagnostics.error(
7295
+ "qoder.environment.packages.unsupported",
7296
+ `environment.${name}: Qoder accepts only apt, npm, and pip package declarations; remove ${unsupported.join(", ")} or install them from setup_script.`,
7297
+ address
7298
+ );
7299
+ }
7300
+ }
7301
+ }
6760
7302
  for (const [name, identity] of Object.entries(config.identities ?? {})) {
6761
7303
  if (identity.provider && identity.provider !== providerName) continue;
6762
7304
  if (!isSupported(caps, "identity")) {
@@ -6778,29 +7320,31 @@ function collectProviderCapabilities(config, providers, diagnostics) {
6778
7320
  continue;
6779
7321
  }
6780
7322
  if (providerName === "qoder") {
6781
- const agent = config.agents?.[channel.agent];
6782
- if (agent?.provider && agent.provider !== providerName) {
6783
- diagnostics.error(
6784
- "config.channel.agent.provider_mismatch",
6785
- `channel.${name}: agent '${channel.agent}' is pinned to provider '${agent.provider}'.`,
6786
- { type: "channel", name, provider: providerName }
6787
- );
6788
- }
6789
- const identityName = channel.identity ?? config.defaults?.identity;
6790
- const identity = identityName ? config.identities?.[identityName] : void 0;
6791
- if (identity?.provider && identity.provider !== providerName) {
6792
- diagnostics.error(
6793
- "config.channel.identity.provider_mismatch",
6794
- `channel.${name}: identity '${identityName}' is pinned to provider '${identity.provider}'.`,
6795
- { type: "channel", name, provider: providerName }
6796
- );
6797
- }
6798
- if (agent && agent.delivery?.qoder?.type !== "forward") {
6799
- diagnostics.error(
6800
- "qoder.channel.forward_template.required",
6801
- `channel.${name}: Qoder Channels require agent '${channel.agent}' to use delivery.qoder.type: forward.`,
6802
- { type: "channel", name, provider: providerName }
6803
- );
7323
+ if (channel.mode !== "pairing" && channel.agent) {
7324
+ const agent = config.agents?.[channel.agent];
7325
+ if (agent?.provider && agent.provider !== providerName) {
7326
+ diagnostics.error(
7327
+ "config.channel.agent.provider_mismatch",
7328
+ `channel.${name}: agent '${channel.agent}' is pinned to provider '${agent.provider}'.`,
7329
+ { type: "channel", name, provider: providerName }
7330
+ );
7331
+ }
7332
+ const identityName = channel.identity ?? config.defaults?.identity;
7333
+ const identity = identityName ? config.identities?.[identityName] : void 0;
7334
+ if (identity?.provider && identity.provider !== providerName) {
7335
+ diagnostics.error(
7336
+ "config.channel.identity.provider_mismatch",
7337
+ `channel.${name}: identity '${identityName}' is pinned to provider '${identity.provider}'.`,
7338
+ { type: "channel", name, provider: providerName }
7339
+ );
7340
+ }
7341
+ if (agent && agent.delivery?.qoder?.type !== "forward") {
7342
+ diagnostics.error(
7343
+ "qoder.channel.forward_template.required",
7344
+ `channel.${name}: Qoder Channels require agent '${channel.agent}' to use delivery.qoder.type: forward.`,
7345
+ { type: "channel", name, provider: providerName }
7346
+ );
7347
+ }
6804
7348
  }
6805
7349
  const requiredCredentials = {
6806
7350
  dingtalk: ["client_id", "client_secret"],
@@ -6831,6 +7375,46 @@ function collectProviderCapabilities(config, providers, diagnostics) {
6831
7375
  }
6832
7376
  }
6833
7377
  }
7378
+ if (providerName === "qoder") {
7379
+ const domains = /* @__PURE__ */ new Map();
7380
+ for (const agent of Object.values(config.agents ?? {})) {
7381
+ if (agent.provider && agent.provider !== providerName) continue;
7382
+ const mode = agent.delivery?.qoder?.type === "forward" ? "forward" : "managed";
7383
+ const refs = [
7384
+ ...agent.environment && !config.environments?.[agent.environment]?.environment_id ? [`environment:${agent.environment}`] : [],
7385
+ ...(agent.skills ?? []).flatMap(
7386
+ (skill) => typeof skill === "string" ? [`skill:${skill}`] : skill.type === "custom" ? [`skill:${skill.skill_id}`] : []
7387
+ ),
7388
+ ...agent.vault ? [`vault:${agent.vault}`] : [],
7389
+ ...(agent.memory_stores ?? []).map((store) => `memory_store:${store}`),
7390
+ ...(agent.files ?? []).map((file) => `file:${file}`)
7391
+ ];
7392
+ for (const ref of refs) {
7393
+ const modes = domains.get(ref) ?? /* @__PURE__ */ new Set();
7394
+ modes.add(mode);
7395
+ domains.set(ref, modes);
7396
+ }
7397
+ }
7398
+ for (const [ref, modes] of domains) {
7399
+ if (modes.size < 2) continue;
7400
+ const [type, name] = ref.split(":");
7401
+ diagnostics.error(
7402
+ `qoder.${type}.delivery_domain.conflict`,
7403
+ `${type}.${name}: referenced by both Managed and Forward agents; declare separate resources because Qoder uses different API domains.`,
7404
+ { type, name, provider: providerName }
7405
+ );
7406
+ }
7407
+ }
7408
+ for (const [name, agent] of Object.entries(config.agents ?? {})) {
7409
+ if (agent.provider && agent.provider !== providerName) continue;
7410
+ if (agent.files?.length && (providerName !== "qoder" || agent.delivery?.qoder?.type !== "forward")) {
7411
+ diagnostics.error(
7412
+ "config.agent.files.unsupported",
7413
+ `agent.${name}: files are supported only by Qoder Forward Templates.`,
7414
+ { type: resolveAgentMaterialization(providerName, agent).resourceType, name, provider: providerName }
7415
+ );
7416
+ }
7417
+ }
6834
7418
  for (const [name, agent] of Object.entries(config.agents ?? {})) {
6835
7419
  if (agent.provider && agent.provider !== providerName) continue;
6836
7420
  const delivery = agent.delivery?.[providerName]?.type ?? "managed";
@@ -6871,6 +7455,44 @@ function collectProviderCapabilities(config, providers, diagnostics) {
6871
7455
  address
6872
7456
  );
6873
7457
  }
7458
+ if (delivery !== "forward" && agent.managed_tool_config) {
7459
+ diagnostics.error(
7460
+ `${providerName}.agent.managed_tool_config.forward_required`,
7461
+ `agent.${name}: managed_tool_config applies to Forward Templates; set delivery.${providerName}.type: forward or remove it.`,
7462
+ address
7463
+ );
7464
+ }
7465
+ if (agent.default_memory_store) {
7466
+ if (providerName !== "qoder" || delivery !== "forward") {
7467
+ diagnostics.error(
7468
+ `${providerName}.agent.default_memory_store.forward_required`,
7469
+ `agent.${name}: default_memory_store is supported only by Qoder Forward delivery.`,
7470
+ address
7471
+ );
7472
+ } else if (!config.defaults?.identity) {
7473
+ diagnostics.error(
7474
+ "qoder.template.default_memory_store.identity.required",
7475
+ `agent.${name}: default_memory_store requires defaults.identity to select the owning Forward Identity.`,
7476
+ { type: "template", name, provider: providerName }
7477
+ );
7478
+ } else {
7479
+ const identity = config.identities?.[config.defaults.identity];
7480
+ if (identity?.provider && identity.provider !== providerName) {
7481
+ diagnostics.error(
7482
+ "qoder.template.default_memory_store.identity.provider_mismatch",
7483
+ `agent.${name}: defaults.identity '${config.defaults.identity}' is pinned to provider '${identity.provider}'.`,
7484
+ { type: "template", name, provider: providerName }
7485
+ );
7486
+ }
7487
+ if (agent.default_memory_store.delete_on_destroy && identity?.identity_id) {
7488
+ diagnostics.error(
7489
+ "qoder.template.default_memory_store.delete.external_identity",
7490
+ `agent.${name}: delete_on_destroy requires an OpenCMA-managed Identity because an external Identity keeps the default Memory Store mounted.`,
7491
+ { type: "template", name, provider: providerName }
7492
+ );
7493
+ }
7494
+ }
7495
+ }
6874
7496
  if (delivery === "forward" && !isSupported(caps, "template")) {
6875
7497
  diagnostics.error(
6876
7498
  `${providerName}.agent.delivery.forward.unsupported`,
@@ -6886,10 +7508,10 @@ function collectProviderCapabilities(config, providers, diagnostics) {
6886
7508
  { type: "template", name, provider: providerName }
6887
7509
  );
6888
7510
  }
6889
- if (agent.memory_stores?.length) {
7511
+ if (agent.memory_stores?.length && !config.defaults?.identity) {
6890
7512
  diagnostics.error(
6891
- "qoder.template.memory_store.unsupported",
6892
- `agent.${name}: memory_stores are not yet supported by Qoder Forward Template delivery.`,
7513
+ "qoder.template.memory_store.identity.required",
7514
+ `agent.${name}: Forward memory_stores require defaults.identity for the Identity/Template mount.`,
6893
7515
  { type: "template", name, provider: providerName }
6894
7516
  );
6895
7517
  }
@@ -6923,6 +7545,68 @@ function collectProviderCapabilities(config, providers, diagnostics) {
6923
7545
  }
6924
7546
  }
6925
7547
  }
7548
+ if (providerName === "bailian") {
7549
+ for (const [name, deployment] of Object.entries(config.deployments ?? {})) {
7550
+ if (deployment.provider && deployment.provider !== providerName) continue;
7551
+ const addr = {
7552
+ type: "deployment",
7553
+ name,
7554
+ provider: providerName
7555
+ };
7556
+ if (deployment.initial_events?.some((event) => event.type === "user.define_outcome")) {
7557
+ diagnostics.warning(
7558
+ `${providerName}.deployment.define_outcome_unsupported`,
7559
+ "Outcome rubrics (user.define_outcome) are dropped from the Bailian deployment payload; the run executes without rubric grading.",
7560
+ addr
7561
+ );
7562
+ }
7563
+ if (!deployment.initial_events?.some((event) => event.type === "user.message" || event.type === "system.message")) {
7564
+ diagnostics.error(
7565
+ `${providerName}.deployment.initial_events.message_required`,
7566
+ `deployment.${name}: Bailian requires at least one user.message or system.message initial event; user.define_outcome events are dropped.`,
7567
+ addr
7568
+ );
7569
+ }
7570
+ if (deployment.resources?.some((resource) => resource.type === "github_repository")) {
7571
+ diagnostics.warning(
7572
+ `${providerName}.deployment.github_repository_unsupported`,
7573
+ "Bailian deployment resources accept files only; github_repository resources are dropped. Clone the repository inside the session instead.",
7574
+ addr
7575
+ );
7576
+ }
7577
+ const mountPrefix = providerMountPrefix(providerName);
7578
+ const normalizedMountPaths = /* @__PURE__ */ new Set();
7579
+ for (const resource of deployment.resources ?? []) {
7580
+ if (resource.type !== "file") continue;
7581
+ if (!resource.mount_path?.trim()) {
7582
+ diagnostics.error(
7583
+ `${providerName}.deployment.file.mount_path.required`,
7584
+ `deployment.${name}: Bailian file resources require mount_path.`,
7585
+ addr
7586
+ );
7587
+ continue;
7588
+ }
7589
+ if (mountPrefix && resource.mount_path.startsWith("/") && resource.mount_path !== mountPrefix && !resource.mount_path.startsWith(`${mountPrefix}/`)) {
7590
+ diagnostics.error(
7591
+ `${providerName}.deployment.file.mount_path.invalid`,
7592
+ `deployment.${name}: Bailian file mount_path must start with '${mountPrefix}/'.`,
7593
+ addr
7594
+ );
7595
+ continue;
7596
+ }
7597
+ const normalizedMountPath = resolveSandboxMountPath(providerName, resource.mount_path);
7598
+ if (normalizedMountPaths.has(normalizedMountPath)) {
7599
+ diagnostics.error(
7600
+ `${providerName}.deployment.file.mount_path.duplicate`,
7601
+ `deployment.${name}: Bailian file mount_path '${normalizedMountPath}' is duplicated after normalization.`,
7602
+ addr
7603
+ );
7604
+ } else {
7605
+ normalizedMountPaths.add(normalizedMountPath);
7606
+ }
7607
+ }
7608
+ }
7609
+ }
6926
7610
  if (providerName !== "qoder") {
6927
7611
  for (const [name, env] of Object.entries(config.environments ?? {})) {
6928
7612
  if (env.environment_id) continue;
@@ -6935,6 +7619,20 @@ function collectProviderCapabilities(config, providers, diagnostics) {
6935
7619
  }
6936
7620
  }
6937
7621
  for (const [name, agent] of Object.entries(config.agents ?? {})) {
7622
+ if (agent.environment_variables && (!agent.provider || agent.provider === providerName)) {
7623
+ diagnostics.error(
7624
+ `${providerName}.agent.environment_variables.unsupported`,
7625
+ `agent.${name}: environment_variables is supported only by Qoder; remove it or pin this agent to qoder.`,
7626
+ { type: "agent", name, provider: providerName }
7627
+ );
7628
+ }
7629
+ if (agent.managed_tool_config && (!agent.provider || agent.provider === providerName)) {
7630
+ diagnostics.error(
7631
+ `${providerName}.agent.managed_tool_config.unsupported`,
7632
+ `agent.${name}: managed_tool_config is supported only by Qoder; remove it or pin this agent to qoder.`,
7633
+ { type: "agent", name, provider: providerName }
7634
+ );
7635
+ }
6938
7636
  if (agent.tunnel && (!agent.provider || agent.provider === providerName)) {
6939
7637
  diagnostics.error(
6940
7638
  `${providerName}.agent.tunnel.unsupported`,
@@ -6995,7 +7693,11 @@ function collectProviderCapabilities(config, providers, diagnostics) {
6995
7693
  if (config.deployments && caps.deployment.tier === "emulated") {
6996
7694
  for (const [name, dep] of Object.entries(config.deployments)) {
6997
7695
  if (dep.provider && dep.provider !== providerName) continue;
6998
- const addr = { type: "deployment", name, provider: providerName };
7696
+ const addr = {
7697
+ type: "deployment",
7698
+ name,
7699
+ provider: providerName
7700
+ };
6999
7701
  if (dep.schedule) {
7000
7702
  diagnostics.warning(
7001
7703
  `${providerName}.deployment.schedule_unsupported`,
@@ -7029,13 +7731,6 @@ function collectProviderCapabilities(config, providers, diagnostics) {
7029
7731
  }
7030
7732
  }
7031
7733
 
7032
- // src/internal/core/agent-materialization.ts
7033
- function resolveAgentMaterialization(provider, agent) {
7034
- const mode = agent.delivery?.[provider]?.type ?? "managed";
7035
- if (mode === "managed") return { resourceType: "agent", mode };
7036
- return { resourceType: "template", mode };
7037
- }
7038
-
7039
7734
  // src/internal/graph/dependency.ts
7040
7735
  function buildDependencyGraph(config, targetProviders) {
7041
7736
  const nodes = /* @__PURE__ */ new Map();
@@ -7103,6 +7798,13 @@ function buildDependencyGraph(config, targetProviders) {
7103
7798
  const materialization = resolveAgentMaterialization(provider, decl);
7104
7799
  const agentAddr = { type: materialization.resourceType, name, provider };
7105
7800
  addNode(agentAddr);
7801
+ if ((decl.default_memory_store || decl.memory_stores?.length) && materialization.resourceType === "template") {
7802
+ const identityName = config.defaults?.identity;
7803
+ if (identityName) {
7804
+ const identityAddr = { type: "identity", name: identityName, provider };
7805
+ if (nodes.has(addressKey(identityAddr))) addEdge(agentAddr, identityAddr);
7806
+ }
7807
+ }
7106
7808
  if (decl.environment && config.environments?.[decl.environment]) {
7107
7809
  const envAddr = {
7108
7810
  type: "environment",
@@ -7129,6 +7831,12 @@ function buildDependencyGraph(config, targetProviders) {
7129
7831
  addEdge(agentAddr, vaultAddr);
7130
7832
  }
7131
7833
  }
7834
+ if (decl.files) {
7835
+ for (const fileName of decl.files) {
7836
+ const fileAddr = { type: "file", name: fileName, provider };
7837
+ if (nodes.has(addressKey(fileAddr))) addEdge(agentAddr, fileAddr);
7838
+ }
7839
+ }
7132
7840
  if (decl.memory_stores) {
7133
7841
  for (const msName of decl.memory_stores) {
7134
7842
  const msAddr = {
@@ -7193,6 +7901,7 @@ function buildDependencyGraph(config, targetProviders) {
7193
7901
  if (decl.provider && decl.provider !== provider) continue;
7194
7902
  const channelAddr = { type: "channel", name, provider };
7195
7903
  addNode(channelAddr);
7904
+ if (decl.mode === "pairing" || !decl.agent) continue;
7196
7905
  const agentDecl = config.agents?.[decl.agent];
7197
7906
  const agentType = agentDecl ? resolveAgentMaterialization(provider, agentDecl).resourceType : "agent";
7198
7907
  const agentAddr = { type: agentType, name: decl.agent, provider };
@@ -7254,6 +7963,7 @@ async function buildPlan(config, state, options = {}) {
7254
7963
  const desiredHash = await computeResourceHash(address, config, options.configPath, hashStateLookup);
7255
7964
  const existing = stateIndex.get(key);
7256
7965
  const deps = getDependencies(address, graph);
7966
+ const needsNativeDeploymentMaterialization = address.type === "deployment" && existing?.remote_id === null && getProvider(address.provider)?.capabilities.deployment.tier === "native";
7257
7967
  if (address.type === "environment" && existing) {
7258
7968
  const envDecl = config.environments?.[address.name];
7259
7969
  if (existing.externally_managed && envDecl && !envDecl.environment_id) {
@@ -7305,6 +8015,17 @@ async function buildPlan(config, state, options = {}) {
7305
8015
  after: { content_hash: desiredHash },
7306
8016
  dependencies: deps
7307
8017
  });
8018
+ } else if (needsNativeDeploymentMaterialization) {
8019
+ actions.push({
8020
+ action: "update",
8021
+ address,
8022
+ driftKind: "none",
8023
+ readinessImpact: "blocking",
8024
+ reason: "Materialize legacy state as a native deployment",
8025
+ before: { content_hash: existing.desired_hash ?? existing.content_hash },
8026
+ after: { content_hash: desiredHash },
8027
+ dependencies: deps
8028
+ });
7308
8029
  } else if ((existing.desired_hash ?? existing.content_hash) !== desiredHash && existing.drift_status === "drifted") {
7309
8030
  const changedPaths = collectChangedPaths(address, config, existing, true);
7310
8031
  actions.push({
@@ -7377,8 +8098,62 @@ async function buildPlan(config, state, options = {}) {
7377
8098
  dependencies: replacement ? [replacement] : []
7378
8099
  });
7379
8100
  }
8101
+ coalesceChannelRenames(actions, config, state);
7380
8102
  return { actions, diagnostics: diagnostics.getAll() };
7381
8103
  }
8104
+ function coalesceChannelRenames(actions, config, state) {
8105
+ const creates = actions.filter((action) => action.action === "create" && action.address.type === "channel");
8106
+ const deletes = actions.filter((action) => action.action === "delete" && action.address.type === "channel");
8107
+ const stateByAddress = new Map(state.resources.map((resource) => [addressKey(resource.address), resource]));
8108
+ const matchedDeletes = /* @__PURE__ */ new Set();
8109
+ for (const create of creates) {
8110
+ const desiredType = config.channels?.[create.address.name]?.type;
8111
+ if (!desiredType) continue;
8112
+ const desiredFingerprint = computeReplacementFingerprint(create.address, config);
8113
+ const candidates = deletes.filter((deletion2) => {
8114
+ if (matchedDeletes.has(deletion2) || deletion2.address.provider !== create.address.provider) return false;
8115
+ const prior2 = stateByAddress.get(addressKey(deletion2.address));
8116
+ const snapshot = prior2?.remote_snapshot;
8117
+ if (snapshot?.channel_type !== desiredType) return false;
8118
+ return !prior2?.replacement_fingerprint || prior2.replacement_fingerprint === desiredFingerprint;
8119
+ });
8120
+ if (candidates.length !== 1) continue;
8121
+ const deletion = candidates[0];
8122
+ const prior = stateByAddress.get(addressKey(deletion.address));
8123
+ const competingCreates = creates.filter(
8124
+ (candidate) => candidate !== create && candidate.address.provider === create.address.provider && config.channels?.[candidate.address.name]?.type === desiredType && (!prior?.replacement_fingerprint || computeReplacementFingerprint(candidate.address, config) === prior.replacement_fingerprint)
8125
+ );
8126
+ if (competingCreates.length > 0) continue;
8127
+ create.action = "update";
8128
+ create.previousAddress = deletion.address;
8129
+ create.before = deletion.before;
8130
+ create.driftKind = "local";
8131
+ create.reason = `Channel key renamed from '${deletion.address.name}' (remote resource retained)`;
8132
+ protectRenamedChannelDependencies(actions, stateByAddress, deletion, create);
8133
+ matchedDeletes.add(deletion);
8134
+ }
8135
+ for (let index = actions.length - 1; index >= 0; index--) {
8136
+ if (matchedDeletes.has(actions[index])) actions.splice(index, 1);
8137
+ }
8138
+ }
8139
+ function protectRenamedChannelDependencies(actions, stateByAddress, deletion, replacement) {
8140
+ const prior = stateByAddress.get(addressKey(deletion.address));
8141
+ const snapshot = prior?.remote_snapshot;
8142
+ const referencedIds = new Set(
8143
+ [snapshot?.identity_id, snapshot?.template_id].filter((id) => typeof id === "string")
8144
+ );
8145
+ if (referencedIds.size === 0) return;
8146
+ for (const action of actions) {
8147
+ if (action.action !== "delete" || action.address.type !== "identity" && action.address.type !== "template" || action.address.provider !== replacement.address.provider) {
8148
+ continue;
8149
+ }
8150
+ const dependency = stateByAddress.get(addressKey(action.address));
8151
+ if (!dependency?.remote_id || !referencedIds.has(dependency.remote_id)) continue;
8152
+ if (!action.dependencies.some((address) => addressKey(address) === addressKey(replacement.address))) {
8153
+ action.dependencies.push(replacement.address);
8154
+ }
8155
+ }
8156
+ }
7382
8157
  function deliveryReplacementAddress(address, graph) {
7383
8158
  if (address.type !== "agent" && address.type !== "template") return void 0;
7384
8159
  const replacementType = address.type === "agent" ? "template" : "agent";
@@ -7467,7 +8242,7 @@ async function refreshState(state, providers, options = {}) {
7467
8242
  dirty = true;
7468
8243
  continue;
7469
8244
  }
7470
- const remote = await provider.findResource(res.address.type, res.address.name, res.remote_id);
8245
+ const remote = await provider.findResource(res.address.type, res.address.name, res.remote_id, res.api_mode);
7471
8246
  if (!remote) {
7472
8247
  if (!options.quiet) {
7473
8248
  emitRuntimeFeedback(options.onFeedback, {
@@ -8025,12 +8800,61 @@ function planDestroyProjectContext(ctx) {
8025
8800
  const resources = [...ctx.state.listResources()].sort(
8026
8801
  (a, b) => (destroyOrder[a.address.type] ?? 99) - (destroyOrder[b.address.type] ?? 99)
8027
8802
  );
8028
- return { resources, executionContext: ctx };
8803
+ const identityName = ctx.config.defaults?.identity;
8804
+ const defaultMemoryStores = [];
8805
+ for (const [agentName, agent] of Object.entries(ctx.config.agents ?? {})) {
8806
+ if (!agent.default_memory_store || agent.delivery?.qoder?.type !== "forward") continue;
8807
+ if (agent.provider && agent.provider !== "qoder") continue;
8808
+ defaultMemoryStores.push({
8809
+ agentName,
8810
+ provider: "qoder",
8811
+ identityId: identityName ? ctx.state.getResource({ type: "identity", name: identityName, provider: "qoder" })?.remote_id ?? null : null,
8812
+ templateId: ctx.state.getResource({ type: "template", name: agentName, provider: "qoder" })?.remote_id ?? null,
8813
+ deleteOnDestroy: agent.default_memory_store.delete_on_destroy ?? false
8814
+ });
8815
+ }
8816
+ for (const pending of ctx.state.getStateFile().pending_default_memory_store_cleanups ?? []) {
8817
+ const existing = defaultMemoryStores.find(
8818
+ (item) => item.agentName === pending.agent_name && item.provider === pending.provider
8819
+ );
8820
+ if (existing) {
8821
+ existing.memoryStoreId = pending.remote_id;
8822
+ existing.deleteOnDestroy = true;
8823
+ } else {
8824
+ defaultMemoryStores.push({
8825
+ agentName: pending.agent_name,
8826
+ provider: pending.provider,
8827
+ identityId: pending.identity_id ?? null,
8828
+ templateId: pending.template_id ?? null,
8829
+ deleteOnDestroy: true,
8830
+ memoryStoreId: pending.remote_id
8831
+ });
8832
+ }
8833
+ }
8834
+ return { resources, defaultMemoryStores, executionContext: ctx };
8029
8835
  }
8030
8836
  async function destroyPlannedProjectResources(planned, options = {}) {
8031
8837
  const results = [];
8032
- let stateChanged = false;
8838
+ const capturedDefaults = await captureDefaultMemoryStores(planned);
8033
8839
  const ctx = planned.executionContext;
8840
+ if (capturedDefaults.some((item) => item.error)) {
8841
+ const defaultMemoryStoreResults2 = capturedDefaults.map(preflightFailureResult);
8842
+ for (const resource of planned.resources) {
8843
+ const result = {
8844
+ resource,
8845
+ status: "blocked",
8846
+ reason: "skipped",
8847
+ error: "Destroy aborted because the default Memory Store preflight failed."
8848
+ };
8849
+ results.push(result);
8850
+ options.onResourceResult?.(result);
8851
+ }
8852
+ return { ...planned, results, defaultMemoryStoreResults: defaultMemoryStoreResults2, destroyed: 0, partial: true };
8853
+ }
8854
+ if (await persistCapturedDefaultMemoryStores(ctx, capturedDefaults)) {
8855
+ await ctx.state.save();
8856
+ }
8857
+ let stateChanged = false;
8034
8858
  for (const resource of planned.resources) {
8035
8859
  options.onResourceStart?.(resource);
8036
8860
  const result = await destroyOneResource(ctx, resource, options);
@@ -8041,14 +8865,171 @@ async function destroyPlannedProjectResources(planned, options = {}) {
8041
8865
  if (stateChanged) {
8042
8866
  await ctx.state.save();
8043
8867
  }
8868
+ const defaultMemoryStoreResults = await finalizeDefaultMemoryStores(planned, capturedDefaults, options);
8869
+ if (recordFailedDefaultMemoryStoreCleanups(ctx, defaultMemoryStoreResults)) {
8870
+ await ctx.state.save();
8871
+ }
8872
+ if (clearCompletedDefaultMemoryStoreCleanups(ctx, defaultMemoryStoreResults)) {
8873
+ await ctx.state.save();
8874
+ }
8044
8875
  const destroyed = results.filter((result) => result.status === "success").length;
8045
8876
  return {
8046
8877
  ...planned,
8047
8878
  results,
8879
+ defaultMemoryStoreResults,
8048
8880
  destroyed,
8049
- partial: destroyed !== planned.resources.length
8881
+ partial: destroyed !== planned.resources.length || defaultMemoryStoreResults.some((result) => result.status === "failed")
8050
8882
  };
8051
8883
  }
8884
+ async function persistCapturedDefaultMemoryStores(ctx, captured) {
8885
+ const state = ctx.state.getStateFile();
8886
+ if (!state.pending_default_memory_store_cleanups) state.pending_default_memory_store_cleanups = [];
8887
+ const pending = state.pending_default_memory_store_cleanups;
8888
+ let changed = false;
8889
+ for (const item of captured) {
8890
+ if (!item.plan.deleteOnDestroy || !item.memoryStoreId) continue;
8891
+ if (pending.some((entry) => entry.provider === item.plan.provider && entry.remote_id === item.memoryStoreId))
8892
+ continue;
8893
+ pending.push({
8894
+ agent_name: item.plan.agentName,
8895
+ provider: item.plan.provider,
8896
+ remote_id: item.memoryStoreId,
8897
+ ...item.plan.identityId ? { identity_id: item.plan.identityId } : {},
8898
+ ...item.plan.templateId ? { template_id: item.plan.templateId } : {}
8899
+ });
8900
+ changed = true;
8901
+ }
8902
+ return changed;
8903
+ }
8904
+ function clearCompletedDefaultMemoryStoreCleanups(ctx, results) {
8905
+ const state = ctx.state.getStateFile();
8906
+ const completedIds = new Set(
8907
+ results.filter((item) => item.status === "deleted" || item.status === "already_gone").map((item) => item.memoryStoreId).filter((id) => Boolean(id))
8908
+ );
8909
+ if (completedIds.size === 0 || !state.pending_default_memory_store_cleanups?.length) return false;
8910
+ const remaining = state.pending_default_memory_store_cleanups.filter((item) => !completedIds.has(item.remote_id));
8911
+ if (remaining.length === state.pending_default_memory_store_cleanups.length) return false;
8912
+ state.pending_default_memory_store_cleanups = remaining;
8913
+ return true;
8914
+ }
8915
+ function recordFailedDefaultMemoryStoreCleanups(ctx, results) {
8916
+ const pending = ctx.state.getStateFile().pending_default_memory_store_cleanups;
8917
+ if (!pending?.length) return false;
8918
+ let changed = false;
8919
+ for (const result of results) {
8920
+ if (result.status !== "failed" || !result.memoryStoreId) continue;
8921
+ const entry = pending.find((item) => item.remote_id === result.memoryStoreId);
8922
+ if (entry && entry.last_error !== result.error) {
8923
+ entry.last_error = result.error;
8924
+ changed = true;
8925
+ }
8926
+ }
8927
+ return changed;
8928
+ }
8929
+ function preflightFailureResult(item) {
8930
+ if (!item.plan.deleteOnDestroy) return { ...item.plan, status: "retained" };
8931
+ return {
8932
+ ...item.plan,
8933
+ status: "failed",
8934
+ ...item.memoryStoreId ? { memoryStoreId: item.memoryStoreId } : {},
8935
+ error: item.error ?? "Destroy aborted because another default Memory Store preflight failed."
8936
+ };
8937
+ }
8938
+ async function captureDefaultMemoryStores(planned) {
8939
+ return Promise.all(
8940
+ planned.defaultMemoryStores.map(async (item) => {
8941
+ if (!item.deleteOnDestroy) return { plan: item };
8942
+ if (item.memoryStoreId) return { plan: item, memoryStoreId: item.memoryStoreId };
8943
+ if (!item.identityId || !item.templateId) {
8944
+ return { plan: item, error: "Cannot resolve the Identity and Template before destroy." };
8945
+ }
8946
+ try {
8947
+ const provider = getRuntimeProvider(planned.executionContext, item.provider);
8948
+ if (!provider.findDefaultMemoryStoreId || !provider.deleteDefaultMemoryStore) {
8949
+ return { plan: item, error: `Provider '${item.provider}' cannot delete a default memory store.` };
8950
+ }
8951
+ const memoryStoreId = await provider.findDefaultMemoryStoreId(item.identityId, item.templateId);
8952
+ return { plan: item, ...memoryStoreId ? { memoryStoreId } : {} };
8953
+ } catch (error) {
8954
+ return { plan: item, error: error instanceof Error ? error.message : String(error) };
8955
+ }
8956
+ })
8957
+ );
8958
+ }
8959
+ async function finalizeDefaultMemoryStores(planned, captured, options) {
8960
+ const output = [];
8961
+ for (const item of captured) {
8962
+ if (!item.plan.deleteOnDestroy) {
8963
+ output.push({ ...item.plan, status: "retained" });
8964
+ continue;
8965
+ }
8966
+ if (item.error) {
8967
+ output.push({ ...item.plan, status: "failed", error: item.error });
8968
+ continue;
8969
+ }
8970
+ if (!item.memoryStoreId) {
8971
+ output.push({ ...item.plan, status: "already_gone" });
8972
+ continue;
8973
+ }
8974
+ try {
8975
+ const provider = getRuntimeProvider(planned.executionContext, item.plan.provider);
8976
+ await deleteDefaultMemoryStoreWithArchiveFallback(
8977
+ provider,
8978
+ item.memoryStoreId,
8979
+ options.defaultMemoryStoreRetryDelaysMs ?? [1e3, 2e3, 4e3, 8e3]
8980
+ );
8981
+ output.push({ ...item.plan, status: "deleted", memoryStoreId: item.memoryStoreId });
8982
+ } catch (error) {
8983
+ if (ApiError.isNotFound(error)) {
8984
+ output.push({ ...item.plan, status: "already_gone", memoryStoreId: item.memoryStoreId });
8985
+ } else {
8986
+ output.push({
8987
+ ...item.plan,
8988
+ status: "failed",
8989
+ memoryStoreId: item.memoryStoreId,
8990
+ error: error instanceof Error ? error.message : String(error)
8991
+ });
8992
+ }
8993
+ }
8994
+ }
8995
+ return output;
8996
+ }
8997
+ async function deleteDefaultMemoryStoreWithArchiveFallback(provider, memoryStoreId, retryDelaysMs) {
8998
+ try {
8999
+ await retryDefaultMemoryStoreOperation(() => provider.deleteDefaultMemoryStore(memoryStoreId), retryDelaysMs);
9000
+ } catch (error) {
9001
+ if (isAlreadyArchivedConflict(error) && provider.deleteMemoryStore) {
9002
+ await retryDefaultMemoryStoreOperation(() => provider.deleteMemoryStore(memoryStoreId), retryDelaysMs);
9003
+ return;
9004
+ }
9005
+ if (!isStillMountedConflict(error) || !provider.archiveMemoryStore || !provider.deleteMemoryStore) throw error;
9006
+ await retryDefaultMemoryStoreOperation(() => provider.archiveMemoryStore(memoryStoreId), retryDelaysMs);
9007
+ await retryDefaultMemoryStoreOperation(() => provider.deleteMemoryStore(memoryStoreId), retryDelaysMs);
9008
+ }
9009
+ }
9010
+ async function retryDefaultMemoryStoreOperation(operation, delaysMs) {
9011
+ for (let attempt = 0; ; attempt++) {
9012
+ try {
9013
+ await operation();
9014
+ return;
9015
+ } catch (error) {
9016
+ if (!isRetryableDefaultMemoryStoreError(error) || attempt >= delaysMs.length) throw error;
9017
+ await delay(delaysMs[attempt]);
9018
+ }
9019
+ }
9020
+ }
9021
+ function isRetryableDefaultMemoryStoreError(error) {
9022
+ return error instanceof ApiError && (isStillMountedConflict(error) || isAlreadyArchivedConflict(error) || error.statusCode === 429 || [500, 502, 503].includes(error.statusCode));
9023
+ }
9024
+ function isStillMountedConflict(error) {
9025
+ return error instanceof ApiError && error.statusCode === 409 && error.responseBody.includes("still mounted");
9026
+ }
9027
+ function isAlreadyArchivedConflict(error) {
9028
+ return error instanceof ApiError && error.statusCode === 409 && error.responseBody.toLowerCase().includes("already archived");
9029
+ }
9030
+ function delay(ms) {
9031
+ return new Promise((resolve13) => setTimeout(resolve13, ms));
9032
+ }
8052
9033
  async function destroyOneResource(ctx, resource, options) {
8053
9034
  if (isExternalReference(ctx, resource)) {
8054
9035
  ctx.state.removeResource(resource.address);
@@ -8069,8 +9050,18 @@ async function destroyOneResource(ctx, resource, options) {
8069
9050
  ctx.state.removeResource(resource.address);
8070
9051
  return successResult(resource, "destroyed");
8071
9052
  }
9053
+ const apiMode = resource.api_mode === "auto" ? void 0 : resource.api_mode;
8072
9054
  try {
8073
- await deleteRemoteResource(provider, resource.address.type, resource.remote_id, options.cascade);
9055
+ await deleteRemoteResource(
9056
+ provider,
9057
+ resource.address.type,
9058
+ resource.remote_id,
9059
+ options.cascade,
9060
+ apiMode,
9061
+ ctx.state.listResources().filter(
9062
+ (candidate) => candidate.address.provider === resource.address.provider && candidate.address.type === "memory_store" && candidate.api_mode === "forward" && typeof candidate.remote_id === "string"
9063
+ ).map((candidate) => candidate.remote_id)
9064
+ );
8074
9065
  ctx.state.removeResource(resource.address);
8075
9066
  emitRuntimeFeedback(options.onFeedback, {
8076
9067
  type: "resource_action_success",
@@ -8099,7 +9090,7 @@ async function destroyOneResource(ctx, resource, options) {
8099
9090
  };
8100
9091
  if (await options.onCascadeRequired?.(blocked)) {
8101
9092
  try {
8102
- await provider.deleteEnvironment(resource.remote_id, true);
9093
+ await provider.deleteEnvironment(resource.remote_id, true, apiMode);
8103
9094
  ctx.state.removeResource(resource.address);
8104
9095
  return {
8105
9096
  ...successResult(resource, "destroyed"),
@@ -8135,29 +9126,29 @@ function failureResult(resource, error) {
8135
9126
  error: error instanceof Error ? error.message : String(error)
8136
9127
  };
8137
9128
  }
8138
- async function deleteRemoteResource(provider, type, id, cascade) {
9129
+ async function deleteRemoteResource(provider, type, id, cascade, mode, ownedMemoryStoreIds = []) {
8139
9130
  switch (type) {
8140
9131
  case "agent":
8141
9132
  await provider.deleteAgent(id);
8142
9133
  return;
8143
9134
  case "template":
8144
9135
  if (!provider.archiveTemplate) throw new UserError(`Provider does not support templates`);
8145
- await provider.archiveTemplate(id);
9136
+ await provider.archiveTemplate(id, ownedMemoryStoreIds);
8146
9137
  return;
8147
9138
  case "skill":
8148
- await provider.deleteSkill(id);
9139
+ await provider.deleteSkill(id, mode);
8149
9140
  return;
8150
9141
  case "memory_store":
8151
9142
  if (!provider.deleteMemoryStore) {
8152
9143
  throw new UserError(`Provider does not support memory stores`);
8153
9144
  }
8154
- await provider.deleteMemoryStore(id);
9145
+ await provider.deleteMemoryStore(id, mode);
8155
9146
  return;
8156
9147
  case "vault":
8157
- await provider.deleteVault(id);
9148
+ await provider.deleteVault(id, mode);
8158
9149
  return;
8159
9150
  case "environment":
8160
- await provider.deleteEnvironment(id, cascade);
9151
+ await provider.deleteEnvironment(id, cascade, mode);
8161
9152
  return;
8162
9153
  case "deployment":
8163
9154
  await provider.deleteDeployment(id);
@@ -8171,7 +9162,7 @@ async function deleteRemoteResource(provider, type, id, cascade) {
8171
9162
  await provider.deleteChannel(id);
8172
9163
  return;
8173
9164
  case "file":
8174
- await provider.deleteFile(id);
9165
+ await provider.deleteFile(id, mode);
8175
9166
  return;
8176
9167
  }
8177
9168
  }
@@ -8374,6 +9365,10 @@ function buildSessionBindings(agentName, config, provider, state, options = {})
8374
9365
  throw new UserError(`Agent '${agentName}' not found in config. Available agents: ${available || "(none)"}`);
8375
9366
  }
8376
9367
  const sessionResources = options.resources ?? agent.resources;
9368
+ const environmentVariables = options.environmentVariables ?? agent.environment_variables;
9369
+ if (environmentVariables && provider !== "qoder") {
9370
+ throw new UserError("Session environment variables are supported only by Qoder.");
9371
+ }
8377
9372
  const providerFeatures = getProvider(provider)?.features;
8378
9373
  for (const resource of sessionResources ?? []) {
8379
9374
  if (!providerFeatures?.session_resources.includes(resource.type)) {
@@ -8402,7 +9397,8 @@ function buildSessionBindings(agentName, config, provider, state, options = {})
8402
9397
  identity_id: identityId,
8403
9398
  files: (options.files ?? []).map((file) => ({ file_id: file.fileId, mount_path: file.mountPath })),
8404
9399
  title: options.title,
8405
- metadata: options.metadata
9400
+ metadata: options.metadata,
9401
+ environment_variables: environmentVariables
8406
9402
  };
8407
9403
  }
8408
9404
  const agentId = requireRef(state, { type: "agent", name: agentName, provider });
@@ -8447,7 +9443,8 @@ function buildSessionBindings(agentName, config, provider, state, options = {})
8447
9443
  files: (options.files ?? []).map((f) => ({ file_id: f.fileId, mount_path: f.mountPath })),
8448
9444
  resources: sessionResources,
8449
9445
  title: options.title,
8450
- metadata: options.metadata
9446
+ metadata: options.metadata,
9447
+ environment_variables: environmentVariables
8451
9448
  };
8452
9449
  }
8453
9450
  function resolveTunnelId(agent, config, options, provider) {
@@ -8848,7 +9845,8 @@ async function createSessionForAgent(ctx, options = {}) {
8848
9845
  files: options.files,
8849
9846
  resources: options.resources,
8850
9847
  title: options.title,
8851
- metadata: options.metadata
9848
+ metadata: options.metadata,
9849
+ environmentVariables: options.environmentVariables
8852
9850
  });
8853
9851
  const session = await adapter2.createSession(bindings);
8854
9852
  return { agentName, provider, session };
@@ -8867,7 +9865,8 @@ async function startSessionRun(ctx, prompt, options = {}) {
8867
9865
  files: options.files,
8868
9866
  resources: options.resources,
8869
9867
  title: options.title,
8870
- metadata: options.metadata
9868
+ metadata: options.metadata,
9869
+ environmentVariables: options.environmentVariables
8871
9870
  });
8872
9871
  const session = await adapter2.createSession(bindings);
8873
9872
  return {
@@ -8928,7 +9927,7 @@ async function collectEventsUntilTerminal(adapter2, sessionId, options = {}) {
8928
9927
  terminalStatus = terminalEvent.status;
8929
9928
  break;
8930
9929
  }
8931
- await delay(currentIntervalMs);
9930
+ await delay2(currentIntervalMs);
8932
9931
  currentIntervalMs = Math.min(currentIntervalMs * 2, maxPollIntervalMs);
8933
9932
  }
8934
9933
  } else {
@@ -8939,7 +9938,7 @@ async function collectEventsUntilTerminal(adapter2, sessionId, options = {}) {
8939
9938
  terminalStatus = session.status;
8940
9939
  break;
8941
9940
  }
8942
- await delay(currentIntervalMs);
9941
+ await delay2(currentIntervalMs);
8943
9942
  currentIntervalMs = Math.min(currentIntervalMs * 2, maxPollIntervalMs);
8944
9943
  }
8945
9944
  result = await adapter2.listSessionEvents(sessionId, { limit: 100 });
@@ -9106,7 +10105,7 @@ async function* streamWithResume(adapter2, sessionId, message) {
9106
10105
  }
9107
10106
  }
9108
10107
  if (!reachedTerminal) {
9109
- await delay(reconnectIntervalMs);
10108
+ await delay2(reconnectIntervalMs);
9110
10109
  reconnectIntervalMs = Math.min(reconnectIntervalMs * 2, DEFAULT_POLL_INTERVAL_MS);
9111
10110
  }
9112
10111
  }
@@ -9134,7 +10133,7 @@ function assertNotTimedOut(start, timeoutMs) {
9134
10133
  throw new UserError(`Session did not complete within the timeout (${Math.floor(timeoutMs / 1e3)} seconds).`);
9135
10134
  }
9136
10135
  }
9137
- function delay(ms) {
10136
+ function delay2(ms) {
9138
10137
  return new Promise((resolve13) => setTimeout(resolve13, ms));
9139
10138
  }
9140
10139
 
@@ -9320,6 +10319,7 @@ var StateManager = class _StateManager {
9320
10319
  address: r.address,
9321
10320
  remote_id: r.remote_id,
9322
10321
  externally_managed: r.externally_managed === true ? true : void 0,
10322
+ api_mode: r.api_mode === "forward" ? "forward" : r.api_mode === "managed" ? "managed" : void 0,
9323
10323
  version: r.version,
9324
10324
  content_hash: r.content_hash ?? r.desired_hash ?? "",
9325
10325
  desired_hash: r.desired_hash ?? r.content_hash ?? "",
@@ -9327,10 +10327,15 @@ var StateManager = class _StateManager {
9327
10327
  desired_readiness_baseline: r.desired_readiness_baseline,
9328
10328
  remote_hash: r.remote_hash,
9329
10329
  remote_snapshot: r.remote_snapshot,
10330
+ replacement_fingerprint: r.replacement_fingerprint,
9330
10331
  drift_paths: r.drift_paths,
9331
10332
  drift_status: r.drift_status
9332
10333
  }));
9333
- return new _StateManager({ resources }, path);
10334
+ const pending = Array.isArray(data.pending_default_memory_store_cleanups) ? data.pending_default_memory_store_cleanups : void 0;
10335
+ return new _StateManager(
10336
+ { resources, ...pending ? { pending_default_memory_store_cleanups: pending } : {} },
10337
+ path
10338
+ );
9334
10339
  } catch (err) {
9335
10340
  if (err && typeof err === "object" && "code" in err && err.code === "ENOENT") {
9336
10341
  return _StateManager.initialize(path);
@@ -9528,6 +10533,8 @@ var PlanReadinessImpactSchema = z6.enum(["none", "non_blocking", "blocking"]);
9528
10533
  var PlannedActionSchema = z6.object({
9529
10534
  action: ActionTypeSchema,
9530
10535
  address: ResourceAddressSchema,
10536
+ /** Existing state address to retain when this action is an inferred logical rename. */
10537
+ previousAddress: ResourceAddressSchema.optional(),
9531
10538
  reason: z6.string(),
9532
10539
  driftKind: DriftKindSchema.optional(),
9533
10540
  readinessImpact: PlanReadinessImpactSchema.optional(),