@h-ai/ai 1.0.0-beta.6 → 1.0.0-beta.7

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
@@ -1,8 +1,8 @@
1
- import { aiM, AIConfigSchema, MemoryConfigSchema, RetrievalConfigSchema, KnowledgeConfigSchema, TokenConfigSchema, SummaryConfigSchema, CompressConfigSchema, AudioConfigSchema, ImageConfigSchema, resolveImageModel, resolveModelEntry, resolveAudioModel, ensureAudioCredential, resolveModelApi } from './chunk-GAC5RLAG.js';
2
- export { A2AConfigSchema, A2ASkillConfigSchema, AIConfigSchema, AUDIO_WS_PATH, ApiTypeSchema, AudioConfigSchema, AudioFormatSchema, AudioModelEntrySchema, AudioOperationSchema, AudioOperationsSchema, AudioProviderSchema, AudioWsClientMessageSchema, AudioWsDoneMessageSchema, AudioWsStartMessageSchema, AudioWsTextMessageSchema, CompressConfigSchema, CompressionStrategySchema, EmbeddingConfigSchema, EntityTypeSchema, FileConfigSchema, ImageConfigSchema, ImageModelEntrySchema, ImageProviderSchema, KnowledgeConfigSchema, LLMConfigSchema, MCPConfigSchema, MCPServerCapabilitiesSchema, MCPServerConfigSchema, MemoryConfigSchema, MemoryTypeSchema, ModelEntrySchema, ModelScenarioSchema, OptionalSecretSchema, RetrievalConfigSchema, RetrievalSourceSchema, SummaryConfigSchema, TokenConfigSchema, ensureAudioCredential, resolveAudioModel, resolveImageModel, resolveModelApi, resolveModelEntry, serializePlayableAudio } from './chunk-GAC5RLAG.js';
3
- import { HaiAIError } from './chunk-HYMP57AC.js';
4
- export { HaiAIError } from './chunk-HYMP57AC.js';
5
- import process from 'process';
1
+ import { aiM, AIConfigSchema, MemoryConfigSchema, ModelEntrySchema, AudioModelEntrySchema, ImageModelEntrySchema, MCPExternalServerSchema, RetrievalSourceSchema, KnowledgeConfigSchema, TokenConfigSchema, SummaryConfigSchema, CompressConfigSchema, ACPConfigSchema, ACPAgentSchema, ACPWorkspaceSchema, AudioConfigSchema, resolveModelEntry, resolveAudioModel, ensureAudioCredential, resolveModelApi, sessionIdentitySchema, ImageConfigSchema, resolveImageModel, ACPPublishSchema, branchNameSchema } from './chunk-HEYVFQC6.js';
2
+ export { A2AConfigSchema, A2ASkillConfigSchema, ACPAgentSchema, ACPConfigSchema, ACPPublishSchema, ACPRepositorySchema, ACPWorkspaceSchema, AIConfigSchema, AUDIO_WS_PATH, ApiTypeSchema, AudioConfigSchema, AudioFormatSchema, AudioModelEntrySchema, AudioOperationSchema, AudioOperationsSchema, AudioProviderSchema, AudioWsClientMessageSchema, AudioWsDoneMessageSchema, AudioWsStartMessageSchema, AudioWsTextMessageSchema, CompressConfigSchema, CompressionStrategySchema, EmbeddingConfigSchema, EntityTypeSchema, FileConfigSchema, ImageConfigSchema, ImageModelEntrySchema, ImageProviderSchema, KnowledgeConfigSchema, LLMConfigSchema, MCPConfigSchema, MCPExternalServerSchema, MCPServerCapabilitiesSchema, MCPServerConfigSchema, MemoryConfigSchema, MemoryTypeSchema, ModelCapabilitiesSchema, ModelEntrySchema, ModelScenarioSchema, OptionalSecretSchema, RetrievalConfigSchema, RetrievalSourceSchema, SummaryConfigSchema, TokenConfigSchema, ensureAudioCredential, resolveAudioModel, resolveImageModel, resolveModelApi, resolveModelEntry, serializePlayableAudio } from './chunk-HEYVFQC6.js';
3
+ import { HaiAIError } from './chunk-M77C3VAP.js';
4
+ export { HaiAIError } from './chunk-M77C3VAP.js';
5
+ import process2 from 'process';
6
6
  import { core, err, ok } from '@h-ai/core';
7
7
  import { datapipe } from '@h-ai/datapipe';
8
8
  import { A2AClient } from '@a2a-js/sdk/client';
@@ -10,14 +10,21 @@ import { JsonRpcTransportHandler, ServerCallContext, A2AError, RequestContext, R
10
10
  import { Buffer } from 'buffer';
11
11
  import { createHash, randomUUID } from 'crypto';
12
12
  import { setTimeout as setTimeout$1 } from 'timers/promises';
13
+ import { z, ZodError } from 'zod';
14
+ import { mkdir, realpath } from 'fs/promises';
15
+ import { isAbsolute, resolve, relative, dirname, basename } from 'path';
16
+ import { spawn } from 'child_process';
17
+ import { Writable, Readable } from 'stream';
18
+ import { methods, ndJsonStream, client, PROTOCOL_VERSION } from '@agentclientprotocol/sdk';
19
+ import { createHttpStream } from '@agentclientprotocol/sdk/experimental/http-client';
20
+ import { createWebSocketStream } from '@agentclientprotocol/sdk/experimental/ws-client';
21
+ import WebSocket2, { WebSocket } from 'ws';
13
22
  import { gunzipSync } from 'zlib';
14
- import WebSocket from 'ws';
15
23
  import { FormData as FormData$1, Agent, fetch as fetch$1 } from 'undici';
16
24
  import OpenAI3, { toFile } from 'openai';
17
25
  import { nanoid } from 'nanoid';
18
26
  import dns from 'dns';
19
27
  import { BlockList, isIP } from 'net';
20
- import { z } from 'zod';
21
28
  import { PromptMessageSchema, TextResourceContentsSchema, BlobResourceContentsSchema } from '@modelcontextprotocol/sdk/types.js';
22
29
  import { AjvJsonSchemaValidator } from '@modelcontextprotocol/sdk/validation/ajv';
23
30
  import { reldb } from '@h-ai/reldb';
@@ -520,7 +527,7 @@ async function executeRemoteAgentCall(remoteUrl, message, options) {
520
527
  safeLabel = validatedUrl.safeLabel;
521
528
  const customHeaders = options?.headers;
522
529
  const timeoutMs = Math.max(1, options?.timeout ?? DEFAULT_A2A_CALL_TIMEOUT_MS);
523
- const client = new A2AClient(requestUrl, {
530
+ const client2 = new A2AClient(requestUrl, {
524
531
  fetchImpl: ((input, init) => {
525
532
  const existingHeaders = init?.headers ? Object.fromEntries(new Headers(init.headers).entries()) : {};
526
533
  const signals = [init?.signal, AbortSignal.timeout(timeoutMs)].filter((signal) => Boolean(signal));
@@ -540,7 +547,7 @@ async function executeRemoteAgentCall(remoteUrl, message, options) {
540
547
  parts: [{ kind: "text", text: message }]
541
548
  }
542
549
  };
543
- const response = await client.sendMessage(params);
550
+ const response = await client2.sendMessage(params);
544
551
  if ("error" in response) {
545
552
  return err(HaiAIError.A2A_REMOTE_CALL_FAILED, aiM("ai_a2aRemoteCallFailed", { params: { url: safeLabel, error: "Remote agent returned an error" } }));
546
553
  }
@@ -702,6 +709,2762 @@ function createA2ALazyProxy(deps) {
702
709
  }
703
710
  };
704
711
  }
712
+ var ACPFailure = class extends Error {
713
+ /**
714
+ * 创建只携带允许公开诊断信息的内部异常。
715
+ * @param kind - 固定错误类别。
716
+ * @param details - 脱敏数据,不得包含凭据或原始 stderr。
717
+ * @returns 内部异常实例。
718
+ * @example
719
+ * ```ts
720
+ * // 内部上下文:runtime/git/connection 为相应工厂已创建的句柄。
721
+ * const failure = new ACPFailure('busy')
722
+ * ```
723
+ */
724
+ constructor(kind, details) {
725
+ super(kind);
726
+ this.kind = kind;
727
+ this.details = details;
728
+ }
729
+ kind;
730
+ details;
731
+ };
732
+ var mappings = {
733
+ invalid: ["ACP_INVALID_REQUEST", "ai_acpInvalid"],
734
+ missing: ["ACP_NOT_FOUND", "ai_acpNotFound"],
735
+ busy: ["ACP_BUSY", "ai_acpBusy"],
736
+ unsupported: ["ACP_UNSUPPORTED", "ai_acpUnsupported"],
737
+ cancelled: ["ACP_CANCELLED", "ai_acpCancelled"],
738
+ timeout: ["ACP_TIMEOUT", "ai_acpTimeout"],
739
+ output: ["ACP_OUTPUT_INVALID", "ai_acpOutput"],
740
+ conflict: ["ACP_CONFLICT", "ai_acpConflict"],
741
+ partial: ["ACP_PARTIAL_FAILURE", "ai_acpPartial"],
742
+ closed: ["ACP_CLOSED", "ai_acpClosed"],
743
+ runtime: ["ACP_RUNTIME_UNAVAILABLE", "ai_acpRuntime"],
744
+ model: ["ACP_MODEL_INCOMPATIBLE", "ai_acpModel"],
745
+ cleanup: ["ACP_CLEANUP_FAILED", "ai_acpCleanup"],
746
+ gitMissing: ["ACP_GIT_NOT_FOUND", "ai_acpGitNotFound"],
747
+ failed: ["ACP_OPERATION_FAILED", "ai_acpOperationFailed"]
748
+ };
749
+ function acpError(error) {
750
+ const failure = error instanceof ACPFailure ? error : new ACPFailure(error instanceof ZodError ? "invalid" : "failed");
751
+ const [code, message] = mappings[failure.kind];
752
+ return err(HaiAIError[code], aiM(message), failure.details);
753
+ }
754
+
755
+ // src/acp/ai-acp-adapters.ts
756
+ function resolveAgentModels(agent, config) {
757
+ return agent.modelIds.map((id) => {
758
+ if (!config.llm?.models?.some((entry2) => entry2.id === id))
759
+ throw new ACPFailure("model", { modelId: id });
760
+ const entry = config.llm.models.find((model) => model.id === id);
761
+ const protocol = entry.protocol ?? config.llm.protocol;
762
+ const llm = protocol === "anthropic" ? {
763
+ ...config.llm,
764
+ apiKey: entry.apiKey ?? config.llm.apiKey ?? (process2.env.HAI_AI_LLM_APIKEY?.trim() || process2.env.ANTHROPIC_API_KEY),
765
+ baseUrl: entry.baseUrl ?? (config.llm.protocol === "anthropic" ? config.llm.baseUrl ?? process2.env.HAI_AI_LLM_BASEURL : void 0) ?? process2.env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com"
766
+ } : config.llm;
767
+ if (protocol === "anthropic" && !llm.apiKey)
768
+ throw new ACPFailure("model", { modelId: id });
769
+ const result = resolveModelEntry(llm, "default", id);
770
+ if (!result.success)
771
+ throw new ACPFailure("model", { modelId: id });
772
+ if (result.data.capabilities?.toolCalling === false)
773
+ throw new ACPFailure("model", { modelId: id });
774
+ return { id, config: result.data };
775
+ });
776
+ }
777
+ function resolveEnvironment(references) {
778
+ const env = {};
779
+ for (const [key, source] of Object.entries(references)) {
780
+ const value = process2.env[source];
781
+ if (!value)
782
+ throw new ACPFailure("invalid", { environment: source });
783
+ env[key] = value;
784
+ }
785
+ return env;
786
+ }
787
+ var nativeAdapters = {
788
+ "deepseek-harness": {
789
+ configure({ agent, models, modelId, statePath }) {
790
+ const selected = models.findIndex((item) => item.id === modelId);
791
+ if (selected < 0)
792
+ throw new ACPFailure("model", { modelId });
793
+ const env = { DSH_HOME: statePath };
794
+ const providers = {};
795
+ for (const [index, entry] of models.entries()) {
796
+ const model = entry.config;
797
+ const caps = model.capabilities;
798
+ if (!caps?.contextWindow || !caps.maxOutputTokens || !caps.inputModalities || caps.toolCalling !== true || !caps.reasoning || caps.reasoning.supported && !caps.reasoning.efforts?.length) {
799
+ throw new ACPFailure("model", { modelId: entry.id });
800
+ }
801
+ if (caps.inputModalities.some((item) => !["text", "image"].includes(item)))
802
+ throw new ACPFailure("model", { modelId: entry.id });
803
+ const key = `HAI_ACP_MODEL_${index}`;
804
+ env[key] = model.apiKey;
805
+ providers[`hai-${index}`] = {
806
+ apiKeyEnv: key,
807
+ baseURL: model.baseUrl,
808
+ api: model.protocol === "chat" ? "openai-completions" : model.protocol === "responses" ? "openai-responses" : "anthropic-messages",
809
+ // 输出容量是能力声明;单轮预算沿用 Hai 模型的 maxTokens。
810
+ defaultMaxTokens: Math.min(model.maxTokens, caps.maxOutputTokens),
811
+ defaultContextWindow: caps.contextWindow,
812
+ timeoutMs: model.timeout,
813
+ retryPolicy: { mode: "normal", maxRetries: 0 },
814
+ models: [
815
+ {
816
+ id: model.model,
817
+ contextWindow: caps.contextWindow,
818
+ input: caps.inputModalities,
819
+ reasoningEfforts: caps.reasoning.supported ? Object.fromEntries(
820
+ (caps.reasoning.efforts ?? []).map((effort) => [
821
+ effort,
822
+ effort
823
+ ])
824
+ ) : false
825
+ }
826
+ ]
827
+ };
828
+ }
829
+ const patch = [
830
+ { id: "llm-pi-ai", config: { providers } },
831
+ {
832
+ id: "acp",
833
+ config: {
834
+ provider: `hai-${selected}`,
835
+ model: models[selected].config.model
836
+ }
837
+ }
838
+ ];
839
+ return {
840
+ command: agent.mode === "custom-service" ? "dsh" : agent.command ?? "dsh",
841
+ args: [
842
+ // 自定义命令可能是 node + JS 入口,入口参数必须先于 dsh 的原生选项。
843
+ ...agent.mode === "custom-service" ? [] : agent.args ?? [],
844
+ "--profile",
845
+ "acp",
846
+ "--patch",
847
+ `${statePath}/hai-config.json`
848
+ ],
849
+ env,
850
+ files: { "hai-config.json": JSON.stringify(patch) }
851
+ };
852
+ }
853
+ },
854
+ "generic": {
855
+ configure({ agent, models }) {
856
+ if (agent.mode === "custom-service" || !agent.command || models.length)
857
+ throw new ACPFailure("model");
858
+ return { command: agent.command, args: agent.args ?? [], env: {} };
859
+ }
860
+ },
861
+ "codex": {
862
+ configure({ agent, models, modelId, statePath }) {
863
+ const selected = models.find((item) => item.id === modelId);
864
+ if (!selected || selected.config.protocol !== "responses")
865
+ throw new ACPFailure("model", { modelId });
866
+ const model = selected.config;
867
+ return {
868
+ command: agent.mode === "custom-service" ? "codex-acp" : agent.command ?? "codex-acp",
869
+ args: agent.mode === "custom-service" ? [] : agent.args ?? [],
870
+ env: {
871
+ CODEX_HOME: statePath,
872
+ CODEX_API_KEY: model.apiKey,
873
+ OPENAI_API_KEY: model.apiKey,
874
+ NO_BROWSER: "1",
875
+ DEFAULT_AUTH_REQUEST: JSON.stringify({ methodId: "api-key" }),
876
+ MODEL_PROVIDER: "hai",
877
+ CODEX_CONFIG: JSON.stringify({
878
+ model: model.model,
879
+ model_provider: "hai",
880
+ model_context_window: model.capabilities?.contextWindow,
881
+ model_providers: {
882
+ hai: {
883
+ name: "Hai",
884
+ base_url: model.baseUrl,
885
+ wire_api: "responses",
886
+ env_key: "CODEX_API_KEY"
887
+ }
888
+ }
889
+ })
890
+ }
891
+ };
892
+ }
893
+ }
894
+ };
895
+ function createResourceRegistry(store, schema, configured, isOpen) {
896
+ const config = new Map(
897
+ configured.map((item) => [item.id, structuredClone(item)])
898
+ );
899
+ if (config.size !== configured.length)
900
+ throw new Error("Duplicate configuration resource ID");
901
+ const copy = (record) => {
902
+ const { __deleted: _deleted, ...value } = record;
903
+ return structuredClone(value);
904
+ };
905
+ function ensure() {
906
+ if (!isOpen())
907
+ throw new Error("Resource registry closed");
908
+ }
909
+ async function read(id) {
910
+ ensure();
911
+ const fixed = config.get(id);
912
+ if (fixed)
913
+ return { value: structuredClone(fixed), source: "config" };
914
+ const saved = await store.readVersioned(id);
915
+ return saved && !saved.data.__deleted ? { value: copy(saved.data), source: "database", version: saved.version } : void 0;
916
+ }
917
+ async function entries() {
918
+ ensure();
919
+ const saved = await store.query({});
920
+ return [
921
+ ...[...config.values()].map((value) => ({
922
+ value: structuredClone(value),
923
+ source: "config"
924
+ })),
925
+ ...saved.filter((item) => !item.__deleted && !config.has(item.id)).map((item) => ({ value: copy(item), source: "database" }))
926
+ ];
927
+ }
928
+ async function protect(operation) {
929
+ if (!isOpen())
930
+ return err(HaiAIError.NOT_INITIALIZED, aiM("ai_notInitialized"));
931
+ try {
932
+ return ok(await operation());
933
+ } catch {
934
+ return err(
935
+ HaiAIError.RESOURCE_OPERATION_FAILED,
936
+ aiM("ai_resourceOperationFailed")
937
+ );
938
+ }
939
+ }
940
+ async function write(input, update, expectedVersion) {
941
+ if (!isOpen())
942
+ return err(HaiAIError.NOT_INITIALIZED, aiM("ai_notInitialized"));
943
+ const parsed = schema.safeParse(input);
944
+ if (!parsed.success || !parsed.data.id)
945
+ return err(HaiAIError.RESOURCE_INVALID, aiM("ai_resourceInvalid"));
946
+ const value = parsed.data;
947
+ if (config.has(value.id))
948
+ return err(HaiAIError.RESOURCE_READ_ONLY, aiM("ai_resourceReadOnly"));
949
+ try {
950
+ const previous = await store.readVersioned(value.id);
951
+ const exists = previous && !previous.data.__deleted;
952
+ if (update && !exists)
953
+ return err(HaiAIError.RESOURCE_NOT_FOUND, aiM("ai_resourceNotFound"));
954
+ if (!update && exists || expectedVersion !== void 0 && expectedVersion !== previous?.version) {
955
+ return err(HaiAIError.RESOURCE_CONFLICT, aiM("ai_resourceConflict"));
956
+ }
957
+ const version = await store.compareAndSwap(value.id, {
958
+ expectedVersion: previous?.version,
959
+ data: value
960
+ });
961
+ if (!version)
962
+ return err(HaiAIError.RESOURCE_CONFLICT, aiM("ai_resourceConflict"));
963
+ return ok({
964
+ value: structuredClone(value),
965
+ source: "database",
966
+ version
967
+ });
968
+ } catch {
969
+ return err(
970
+ HaiAIError.RESOURCE_OPERATION_FAILED,
971
+ aiM("ai_resourceOperationFailed")
972
+ );
973
+ }
974
+ }
975
+ const operations = {
976
+ list: () => protect(entries),
977
+ get: async (id) => {
978
+ const result = await protect(() => read(id));
979
+ if (!result.success)
980
+ return result;
981
+ return result.data ? ok(result.data) : err(HaiAIError.RESOURCE_NOT_FOUND, aiM("ai_resourceNotFound"));
982
+ },
983
+ register: (input) => write(input, false),
984
+ update: (input, version) => write(input, true, version),
985
+ remove: async (id, expectedVersion) => {
986
+ if (!isOpen())
987
+ return err(HaiAIError.NOT_INITIALIZED, aiM("ai_notInitialized"));
988
+ if (config.has(id))
989
+ return err(HaiAIError.RESOURCE_READ_ONLY, aiM("ai_resourceReadOnly"));
990
+ try {
991
+ const previous = await store.readVersioned(id);
992
+ if (!previous || previous.data.__deleted)
993
+ return err(HaiAIError.RESOURCE_NOT_FOUND, aiM("ai_resourceNotFound"));
994
+ if (expectedVersion !== void 0 && expectedVersion !== previous.version) {
995
+ return err(HaiAIError.RESOURCE_CONFLICT, aiM("ai_resourceConflict"));
996
+ }
997
+ if (!await store.compareAndSwap(id, {
998
+ expectedVersion: previous.version,
999
+ data: { ...previous.data, __deleted: true }
1000
+ })) {
1001
+ return err(HaiAIError.RESOURCE_CONFLICT, aiM("ai_resourceConflict"));
1002
+ }
1003
+ return ok(void 0);
1004
+ } catch {
1005
+ return err(
1006
+ HaiAIError.RESOURCE_OPERATION_FAILED,
1007
+ aiM("ai_resourceOperationFailed")
1008
+ );
1009
+ }
1010
+ }
1011
+ };
1012
+ return {
1013
+ operations,
1014
+ read,
1015
+ entries,
1016
+ values: async () => (await entries()).map((entry) => entry.value)
1017
+ };
1018
+ }
1019
+ var nativeJob = String.raw`
1020
+ using System;
1021
+ using System.ComponentModel;
1022
+ using System.Runtime.InteropServices;
1023
+ using System.Text;
1024
+ using System.Threading;
1025
+
1026
+ public static class HaiACPCommandJob {
1027
+ [StructLayout(LayoutKind.Sequential)] struct BasicLimits {
1028
+ public long ProcessTime, JobTime;
1029
+ public uint Flags;
1030
+ public UIntPtr MinimumWorkingSet, MaximumWorkingSet;
1031
+ public uint ActiveProcessLimit;
1032
+ public UIntPtr Affinity;
1033
+ public uint PriorityClass, SchedulingClass;
1034
+ }
1035
+ [StructLayout(LayoutKind.Sequential)] struct IoCounters {
1036
+ public ulong ReadOperations, WriteOperations, OtherOperations, ReadBytes, WriteBytes, OtherBytes;
1037
+ }
1038
+ [StructLayout(LayoutKind.Sequential)] struct ExtendedLimits {
1039
+ public BasicLimits Basic;
1040
+ public IoCounters Io;
1041
+ public UIntPtr ProcessMemory, JobMemory, PeakProcessMemory, PeakJobMemory;
1042
+ }
1043
+ [StructLayout(LayoutKind.Sequential)] struct Accounting {
1044
+ public long UserTime, KernelTime, PeriodUserTime, PeriodKernelTime;
1045
+ public uint Faults, Total, Active, Terminated;
1046
+ }
1047
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] struct Startup {
1048
+ public uint Size;
1049
+ public string Reserved, Desktop, Title;
1050
+ public uint X, Y, Width, Height, XChars, YChars, Fill, Flags;
1051
+ public ushort Show, ReservedSize;
1052
+ public IntPtr ReservedData, Input, Output, Error;
1053
+ }
1054
+ [StructLayout(LayoutKind.Sequential)] struct ProcessInfo {
1055
+ public IntPtr Process, Thread;
1056
+ public uint ProcessId, ThreadId;
1057
+ }
1058
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] static extern IntPtr CreateJobObject(IntPtr attributes, string name);
1059
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] static extern IntPtr OpenJobObject(uint access, bool inherit, string name);
1060
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool SetInformationJobObject(IntPtr job, int kind, ref ExtendedLimits information, uint size);
1061
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool QueryInformationJobObject(IntPtr job, int kind, out Accounting information, uint size, IntPtr length);
1062
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);
1063
+ [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] static extern bool CreateProcess(string application, StringBuilder command, IntPtr processAttributes, IntPtr threadAttributes, bool inherit, uint flags, IntPtr environment, string directory, ref Startup startup, out ProcessInfo information);
1064
+ [DllImport("kernel32.dll", SetLastError = true)] static extern uint ResumeThread(IntPtr thread);
1065
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool TerminateProcess(IntPtr process, uint code);
1066
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool TerminateJobObject(IntPtr job, uint code);
1067
+ [DllImport("kernel32.dll", SetLastError = true)] static extern uint WaitForSingleObject(IntPtr handle, uint milliseconds);
1068
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool GetExitCodeProcess(IntPtr process, out uint code);
1069
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool CloseHandle(IntPtr handle);
1070
+ [DllImport("kernel32.dll", SetLastError = true)] static extern IntPtr GetStdHandle(int kind);
1071
+ [DllImport("kernel32.dll", SetLastError = true)] static extern bool SetHandleInformation(IntPtr handle, uint mask, uint flags);
1072
+
1073
+ static void Check(bool success) { if (!success) throw new Win32Exception(Marshal.GetLastWin32Error()); }
1074
+ static uint Active(IntPtr job) {
1075
+ Accounting information;
1076
+ Check(QueryInformationJobObject(job, 1, out information, (uint)Marshal.SizeOf(typeof(Accounting)), IntPtr.Zero));
1077
+ return information.Active;
1078
+ }
1079
+ // Windows argv 编码:引号前与结尾的反斜杠加倍,其余字符原样传递,不经过命令 shell。
1080
+ static string Quote(string value) {
1081
+ StringBuilder result = new StringBuilder("\"");
1082
+ int slashes = 0;
1083
+ foreach (char current in value) {
1084
+ if (current == '\\') { slashes++; continue; }
1085
+ if (current == '"') result.Append('\\', slashes * 2 + 1);
1086
+ else result.Append('\\', slashes);
1087
+ result.Append(current);
1088
+ slashes = 0;
1089
+ }
1090
+ result.Append('\\', slashes * 2).Append('"');
1091
+ return result.ToString();
1092
+ }
1093
+ public static int Run(string name, string command, string[] arguments) {
1094
+ IntPtr job = CreateJobObject(IntPtr.Zero, name);
1095
+ if (job == IntPtr.Zero) throw new Win32Exception(Marshal.GetLastWin32Error());
1096
+ ProcessInfo process = new ProcessInfo();
1097
+ bool assigned = false;
1098
+ try {
1099
+ ExtendedLimits limits = new ExtendedLimits();
1100
+ limits.Basic.Flags = 0x2000; // JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;不允许 breakaway。
1101
+ Check(SetInformationJobObject(job, 9, ref limits, (uint)Marshal.SizeOf(typeof(ExtendedLimits))));
1102
+ StringBuilder line = new StringBuilder(Quote(command));
1103
+ foreach (string argument in arguments) line.Append(' ').Append(Quote(argument));
1104
+ Startup startup = new Startup();
1105
+ startup.Size = (uint)Marshal.SizeOf(typeof(Startup));
1106
+ startup.Flags = 0x100;
1107
+ startup.Input = GetStdHandle(-10); startup.Output = GetStdHandle(-11); startup.Error = GetStdHandle(-12);
1108
+ Check(SetHandleInformation(startup.Input, 1, 1));
1109
+ Check(SetHandleInformation(startup.Output, 1, 1));
1110
+ Check(SetHandleInformation(startup.Error, 1, 1));
1111
+ Check(CreateProcess(null, line, IntPtr.Zero, IntPtr.Zero, true, 0x08000004, IntPtr.Zero, null, ref startup, out process));
1112
+ Check(AssignProcessToJobObject(job, process.Process));
1113
+ assigned = true;
1114
+ if (ResumeThread(process.Thread) == 0xFFFFFFFF) throw new Win32Exception(Marshal.GetLastWin32Error());
1115
+ if (WaitForSingleObject(process.Process, 0xFFFFFFFF) != 0) throw new Win32Exception(Marshal.GetLastWin32Error());
1116
+ uint code;
1117
+ Check(GetExitCodeProcess(process.Process, out code));
1118
+ while (Active(job) != 0) Thread.Sleep(20);
1119
+ return unchecked((int)code);
1120
+ }
1121
+ finally {
1122
+ if (process.Process != IntPtr.Zero) {
1123
+ // 分配失败时进程仍挂起,直接终止,不允许裸进程恢复;Job 在其他异常时同样清空。
1124
+ if (!assigned) TerminateProcess(process.Process, 125);
1125
+ else TerminateJobObject(job, 125);
1126
+ WaitForSingleObject(process.Process, 5000);
1127
+ }
1128
+ if (process.Thread != IntPtr.Zero) CloseHandle(process.Thread);
1129
+ if (process.Process != IntPtr.Zero) CloseHandle(process.Process);
1130
+ CloseHandle(job);
1131
+ }
1132
+ }
1133
+ public static bool Empty(string name) {
1134
+ IntPtr job = OpenJobObject(4, false, name); // JOB_OBJECT_QUERY
1135
+ if (job == IntPtr.Zero) {
1136
+ if (Marshal.GetLastWin32Error() == 2) return true;
1137
+ throw new Win32Exception(Marshal.GetLastWin32Error());
1138
+ }
1139
+ try { return Active(job) == 0; }
1140
+ finally { CloseHandle(job); }
1141
+ }
1142
+ }
1143
+ `;
1144
+ function powershell(script) {
1145
+ return ["-NoLogo", "-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")];
1146
+ }
1147
+ function windowsACPCommand(command, args) {
1148
+ const name = `Local\\hai-ai-acp-${randomUUID()}`;
1149
+ const key = `__HAI_AI_ACP_JOB_${randomUUID().replaceAll("-", "")}`;
1150
+ const script = `$ErrorActionPreference='Stop'; $ProgressPreference='SilentlyContinue'; $InformationPreference='SilentlyContinue'
1151
+ try {
1152
+ Add-Type -TypeDefinition @'
1153
+ ${nativeJob}
1154
+ '@
1155
+ $inputConfig = [Environment]::GetEnvironmentVariable('${key}') | ConvertFrom-Json
1156
+ [Environment]::SetEnvironmentVariable('${key}', $null)
1157
+ exit ([HaiACPCommandJob]::Run([string]$inputConfig.name, [string]$inputConfig.command, [string[]]$inputConfig.args))
1158
+ } catch { [Console]::Error.WriteLine('Windows ACP job failed'); exit 125 }`;
1159
+ return { name, args: powershell(script), env: { [key]: JSON.stringify({ name, command, args }) } };
1160
+ }
1161
+ function windowsACPJobProbe(name) {
1162
+ return powershell(`$ErrorActionPreference='Stop'; $ProgressPreference='SilentlyContinue'; $InformationPreference='SilentlyContinue'
1163
+ try {
1164
+ Add-Type -TypeDefinition @'
1165
+ ${nativeJob}
1166
+ '@
1167
+ if ([HaiACPCommandJob]::Empty('${name}')) { exit 0 } else { exit 1 }
1168
+ } catch { exit 2 }`);
1169
+ }
1170
+ async function jobProbe(name, timeout) {
1171
+ return new Promise((resolve3) => {
1172
+ const probe = spawn("powershell.exe", windowsACPJobProbe(name), { shell: false, windowsHide: true, stdio: "ignore", env: baseEnvironment() });
1173
+ let settled = false;
1174
+ let timer;
1175
+ const finish = (code) => {
1176
+ if (settled)
1177
+ return;
1178
+ settled = true;
1179
+ clearTimeout(timer);
1180
+ resolve3(code);
1181
+ };
1182
+ timer = setTimeout(() => {
1183
+ void stopProcess(probe).catch(() => {
1184
+ }).finally(() => finish(2));
1185
+ probe.kill("SIGKILL");
1186
+ finish(2);
1187
+ }, Math.max(1, timeout));
1188
+ probe.once("error", () => finish(2));
1189
+ probe.once("close", (code) => finish(code ?? 2));
1190
+ });
1191
+ }
1192
+ async function stopWindowsJob(child, name, closeObserved) {
1193
+ const deadline = performance.now() + 15e3;
1194
+ let timer;
1195
+ try {
1196
+ await Promise.race([
1197
+ stopProcess(child, { closeObserved }),
1198
+ new Promise((_, reject) => {
1199
+ timer = setTimeout(() => {
1200
+ child.kill("SIGKILL");
1201
+ reject(new ACPFailure("cleanup"));
1202
+ }, Math.max(1, deadline - performance.now()));
1203
+ })
1204
+ ]);
1205
+ while (performance.now() < deadline) {
1206
+ const state = await jobProbe(name, deadline - performance.now());
1207
+ if (state === 0)
1208
+ return;
1209
+ if (state !== 1)
1210
+ throw new ACPFailure("cleanup");
1211
+ await new Promise((resolve3) => setTimeout(resolve3, 20));
1212
+ }
1213
+ throw new ACPFailure("cleanup");
1214
+ } finally {
1215
+ clearTimeout(timer);
1216
+ }
1217
+ }
1218
+ async function stopProcess(child, options = {}) {
1219
+ if (process2.platform === "win32" && options.windowsJob)
1220
+ return stopWindowsJob(child, options.windowsJob, options.closeObserved);
1221
+ const exited = child.exitCode !== null || child.signalCode !== null;
1222
+ if (!child.pid || exited && !options.ownedTree)
1223
+ return;
1224
+ if (process2.platform === "win32" && exited && options.ownedTree)
1225
+ throw new ACPFailure("cleanup");
1226
+ const closed = options.closeObserved ? Promise.resolve() : new Promise((resolve3) => child.once("close", () => resolve3()));
1227
+ if (process2.platform === "win32") {
1228
+ await new Promise((resolve3, reject) => {
1229
+ const killer = spawn(
1230
+ "taskkill",
1231
+ ["/PID", String(child.pid), "/T", "/F"],
1232
+ { windowsHide: true, stdio: "ignore" }
1233
+ );
1234
+ killer.once("error", reject);
1235
+ killer.once("exit", (code) => code === 0 || !options.ownedTree && child.exitCode !== null ? resolve3() : reject(new ACPFailure("cleanup")));
1236
+ });
1237
+ } else {
1238
+ try {
1239
+ process2.kill(-child.pid, "SIGKILL");
1240
+ } catch (error) {
1241
+ if (error.code !== "ESRCH")
1242
+ throw new ACPFailure("cleanup");
1243
+ }
1244
+ }
1245
+ await closed;
1246
+ }
1247
+ function baseEnvironment() {
1248
+ const output = {};
1249
+ for (const [key, value] of Object.entries(process2.env)) {
1250
+ if (value !== void 0 && /^(?:path|pathext|systemroot|windir|comspec|temp|tmp|lang|lc_all|ssl_cert_file|node_extra_ca_certs)$/i.test(
1251
+ key
1252
+ )) {
1253
+ output[key] = value;
1254
+ }
1255
+ }
1256
+ return output;
1257
+ }
1258
+ function engineEnvironment() {
1259
+ const output = baseEnvironment();
1260
+ for (const [key, value] of Object.entries(process2.env)) {
1261
+ if (value !== void 0 && /^(?:HOME|USERPROFILE|APPDATA|LOCALAPPDATA|XDG_CONFIG_HOME|XDG_RUNTIME_DIR|DOCKER_HOST|DOCKER_CONTEXT|DOCKER_CONFIG|CONTAINER_HOST|CONTAINER_CONNECTION|SSH_AUTH_SOCK)$/i.test(
1262
+ key
1263
+ )) {
1264
+ output[key] = value;
1265
+ }
1266
+ }
1267
+ return output;
1268
+ }
1269
+ async function execute(command, args, cwd, env, timeout = 12e4) {
1270
+ return await new Promise((resolve3, reject) => {
1271
+ const child = spawn(command, args, {
1272
+ cwd,
1273
+ env: {
1274
+ ...command === "podman" || command === "docker" ? engineEnvironment() : baseEnvironment(),
1275
+ ...env
1276
+ },
1277
+ shell: false,
1278
+ detached: process2.platform !== "win32",
1279
+ windowsHide: true,
1280
+ stdio: ["ignore", "pipe", "pipe"]
1281
+ });
1282
+ let output = "";
1283
+ let failure;
1284
+ const stop = (error) => {
1285
+ failure ??= error;
1286
+ void stopProcess(child).catch((error2) => {
1287
+ failure = error2;
1288
+ });
1289
+ };
1290
+ const timer = setTimeout(() => stop(new ACPFailure("timeout")), timeout);
1291
+ child.stdout.on("data", (chunk) => {
1292
+ output += chunk.toString("utf8");
1293
+ if (Buffer.byteLength(output) > 8 * 1024 * 1024)
1294
+ stop(new ACPFailure("output"));
1295
+ });
1296
+ child.stderr.resume();
1297
+ child.once("error", (error) => {
1298
+ failure = new ACPFailure(
1299
+ command === "git" && error.code === "ENOENT" ? "gitMissing" : "runtime"
1300
+ );
1301
+ });
1302
+ child.once("close", (code) => {
1303
+ clearTimeout(timer);
1304
+ if (failure)
1305
+ reject(failure);
1306
+ else if (code !== 0)
1307
+ reject(new ACPFailure("failed", { exitCode: code }));
1308
+ else resolve3(output.trim());
1309
+ });
1310
+ });
1311
+ }
1312
+
1313
+ // src/acp/ai-acp-client.ts
1314
+ async function connectACP(transport, options = {}, inputLimits) {
1315
+ const limits = ACPConfigSchema.parse(inputLimits ?? {});
1316
+ const handlers = options.handlers ?? {};
1317
+ const capabilities = options.capabilities ?? {};
1318
+ const required2 = [];
1319
+ if (capabilities.fs?.readTextFile)
1320
+ required2.push(methods.client.fs.readTextFile);
1321
+ if (capabilities.fs?.writeTextFile)
1322
+ required2.push(methods.client.fs.writeTextFile);
1323
+ if (capabilities.terminal)
1324
+ required2.push(...Object.values(methods.client.terminal));
1325
+ if (capabilities.elicitation)
1326
+ required2.push(methods.client.elicitation.create);
1327
+ if (required2.some((name) => !Object.hasOwn(handlers, name))) {
1328
+ throw new ACPFailure("invalid");
1329
+ }
1330
+ if (options.signal?.aborted)
1331
+ throw new ACPFailure("cancelled");
1332
+ let stop = async () => {
1333
+ };
1334
+ let stream;
1335
+ if (transport.type === "stdio") {
1336
+ const job = process2.platform === "win32" ? windowsACPCommand(transport.command, transport.args ?? []) : void 0;
1337
+ const child = spawn(job ? "powershell.exe" : transport.command, job?.args ?? transport.args ?? [], {
1338
+ cwd: transport.cwd,
1339
+ env: { ...baseEnvironment(), ...transport.env, ...job?.env },
1340
+ detached: process2.platform !== "win32",
1341
+ shell: false,
1342
+ windowsHide: true,
1343
+ stdio: ["pipe", "pipe", "pipe"]
1344
+ });
1345
+ let childClosed = false;
1346
+ child.once("close", () => {
1347
+ childClosed = true;
1348
+ });
1349
+ child.stderr.resume();
1350
+ await new Promise((resolve3, reject) => {
1351
+ child.once("spawn", resolve3);
1352
+ child.once("error", () => reject(new ACPFailure("runtime")));
1353
+ });
1354
+ stop = () => stopProcess(child, { ownedTree: true, closeObserved: childClosed, windowsJob: job?.name });
1355
+ stream = ndJsonStream(
1356
+ Writable.toWeb(child.stdin),
1357
+ Readable.toWeb(child.stdout),
1358
+ { maxMessageBytes: limits.maxContentBytes }
1359
+ );
1360
+ } else if (transport.type === "http") {
1361
+ stream = createHttpStream(transport.url, {
1362
+ headers: transport.headers,
1363
+ maxMessageBytes: limits.maxContentBytes
1364
+ });
1365
+ } else {
1366
+ class BoundedWebSocket extends WebSocket {
1367
+ constructor(url, protocols, options2) {
1368
+ super(url, protocols, {
1369
+ ...options2,
1370
+ maxPayload: limits.maxContentBytes
1371
+ });
1372
+ }
1373
+ }
1374
+ stream = createWebSocketStream(transport.url, {
1375
+ headers: transport.headers,
1376
+ WebSocket: BoundedWebSocket
1377
+ });
1378
+ }
1379
+ const requests = /* @__PURE__ */ new Set();
1380
+ const originalWriter = stream.writable.getWriter();
1381
+ const guardedStream = {
1382
+ writable: new WritableStream({
1383
+ async write(message) {
1384
+ if ("method" in message && "id" in message && message.id !== void 0 && message.id !== null) {
1385
+ requests.add(message.id);
1386
+ }
1387
+ await originalWriter.write(message);
1388
+ },
1389
+ close: () => originalWriter.close(),
1390
+ abort: (reason) => originalWriter.abort(reason)
1391
+ }),
1392
+ readable: stream.readable.pipeThrough(
1393
+ new TransformStream({
1394
+ transform(message, controller) {
1395
+ if (!("method" in message) && "id" in message && message.id !== void 0 && message.id !== null) {
1396
+ requests.delete(message.id);
1397
+ }
1398
+ controller.enqueue(message);
1399
+ }
1400
+ })
1401
+ )
1402
+ };
1403
+ let sessionId;
1404
+ let sessionOpening = false;
1405
+ let closed = false;
1406
+ let failure;
1407
+ let closePromise;
1408
+ const listeners = /* @__PURE__ */ new Set();
1409
+ const app = client({ name: "hai-ai" });
1410
+ app.onNotification(methods.client.session.update, ({ params }) => {
1411
+ if (sessionId && params.sessionId !== sessionId)
1412
+ return;
1413
+ try {
1414
+ options.onEvent?.(params);
1415
+ for (const listener of listeners) listener(params);
1416
+ } catch (error) {
1417
+ failure = error;
1418
+ void close().catch(() => {
1419
+ });
1420
+ }
1421
+ });
1422
+ function owned(id) {
1423
+ if (!sessionId || id !== sessionId)
1424
+ throw new ACPFailure("missing");
1425
+ }
1426
+ app.onRequest(methods.client.session.requestPermission, async (context) => {
1427
+ owned(context.params.sessionId);
1428
+ const handler = handlers["session/request_permission"];
1429
+ if (!handler)
1430
+ return { outcome: { outcome: "cancelled" } };
1431
+ return handler(context);
1432
+ });
1433
+ app.onRequest(methods.client.fs.readTextFile, async (context) => {
1434
+ owned(context.params.sessionId);
1435
+ const handler = handlers["fs/read_text_file"];
1436
+ if (!handler)
1437
+ throw new ACPFailure("unsupported");
1438
+ return handler(context);
1439
+ });
1440
+ app.onRequest(methods.client.fs.writeTextFile, async (context) => {
1441
+ owned(context.params.sessionId);
1442
+ const handler = handlers["fs/write_text_file"];
1443
+ if (!handler)
1444
+ throw new ACPFailure("unsupported");
1445
+ return handler(context);
1446
+ });
1447
+ app.onRequest(methods.client.terminal.create, async (context) => {
1448
+ owned(context.params.sessionId);
1449
+ const handler = handlers["terminal/create"];
1450
+ if (!handler)
1451
+ throw new ACPFailure("unsupported");
1452
+ return handler(context);
1453
+ });
1454
+ app.onRequest(methods.client.terminal.output, async (context) => {
1455
+ owned(context.params.sessionId);
1456
+ const handler = handlers["terminal/output"];
1457
+ if (!handler)
1458
+ throw new ACPFailure("unsupported");
1459
+ return handler(context);
1460
+ });
1461
+ app.onRequest(methods.client.terminal.release, async (context) => {
1462
+ owned(context.params.sessionId);
1463
+ const handler = handlers["terminal/release"];
1464
+ if (!handler)
1465
+ throw new ACPFailure("unsupported");
1466
+ return handler(context);
1467
+ });
1468
+ app.onRequest(methods.client.terminal.waitForExit, async (context) => {
1469
+ owned(context.params.sessionId);
1470
+ const handler = handlers["terminal/wait_for_exit"];
1471
+ if (!handler)
1472
+ throw new ACPFailure("unsupported");
1473
+ return handler(context);
1474
+ });
1475
+ app.onRequest(methods.client.terminal.kill, async (context) => {
1476
+ owned(context.params.sessionId);
1477
+ const handler = handlers["terminal/kill"];
1478
+ if (!handler)
1479
+ throw new ACPFailure("unsupported");
1480
+ return handler(context);
1481
+ });
1482
+ app.onRequest(methods.client.elicitation.create, async (context) => {
1483
+ if ("sessionId" in context.params && typeof context.params.sessionId === "string") {
1484
+ owned(context.params.sessionId);
1485
+ } else if (!("requestId" in context.params) || typeof context.params.requestId !== "string" && typeof context.params.requestId !== "number" || !requests.has(context.params.requestId)) {
1486
+ throw new ACPFailure("missing");
1487
+ }
1488
+ const handler = handlers["elicitation/create"];
1489
+ if (!handler)
1490
+ throw new ACPFailure("unsupported");
1491
+ return handler(context);
1492
+ });
1493
+ const connection = app.connect(guardedStream);
1494
+ function close() {
1495
+ closed = true;
1496
+ if (!closePromise) {
1497
+ const task = (async () => {
1498
+ try {
1499
+ await stop();
1500
+ } finally {
1501
+ connection.close();
1502
+ }
1503
+ })();
1504
+ closePromise = task;
1505
+ void task.catch(() => {
1506
+ if (closePromise === task)
1507
+ closePromise = void 0;
1508
+ });
1509
+ }
1510
+ return closePromise;
1511
+ }
1512
+ async function request(operation, timeout = limits.requestTimeout, signal, prompt = false, isCancelledResponse) {
1513
+ if (closed)
1514
+ throw new ACPFailure("closed");
1515
+ if (signal?.aborted)
1516
+ throw new ACPFailure("cancelled");
1517
+ let timer;
1518
+ let abort = () => {
1519
+ };
1520
+ let reason;
1521
+ const deadline = new Promise((_, reject) => {
1522
+ const fail = (kind) => {
1523
+ if (reason)
1524
+ return;
1525
+ reason = new ACPFailure(kind);
1526
+ if (prompt && sessionId) {
1527
+ void connection.agent.notify(methods.agent.session.cancel, { sessionId }).catch(() => {
1528
+ });
1529
+ }
1530
+ clearTimeout(timer);
1531
+ timer = setTimeout(
1532
+ () => {
1533
+ void close().then(() => reject(reason), reject);
1534
+ },
1535
+ prompt ? limits.cancelTimeout : 0
1536
+ );
1537
+ };
1538
+ timer = setTimeout(fail, timeout, "timeout");
1539
+ abort = () => fail("cancelled");
1540
+ signal?.addEventListener("abort", abort, { once: true });
1541
+ });
1542
+ try {
1543
+ const output = await Promise.race([operation(), deadline]);
1544
+ if (failure)
1545
+ throw failure;
1546
+ if (reason) {
1547
+ if (!(reason.kind === "cancelled" && prompt && isCancelledResponse?.(output)))
1548
+ await close();
1549
+ throw reason;
1550
+ }
1551
+ return output;
1552
+ } catch (error) {
1553
+ if (failure)
1554
+ throw failure;
1555
+ if (reason)
1556
+ throw reason;
1557
+ throw error;
1558
+ } finally {
1559
+ clearTimeout(timer);
1560
+ signal?.removeEventListener("abort", abort);
1561
+ }
1562
+ }
1563
+ try {
1564
+ let checkMcp2 = function(servers) {
1565
+ for (const server of servers) {
1566
+ if ("type" in server && server.type === "http" && !agentCapabilities.mcpCapabilities?.http) {
1567
+ throw new ACPFailure("unsupported");
1568
+ }
1569
+ if ("type" in server && server.type === "sse" && !agentCapabilities.mcpCapabilities?.sse) {
1570
+ throw new ACPFailure("unsupported");
1571
+ }
1572
+ }
1573
+ };
1574
+ var checkMcp = checkMcp2;
1575
+ const initialized = await request(
1576
+ () => connection.agent.request(methods.agent.initialize, {
1577
+ protocolVersion: PROTOCOL_VERSION,
1578
+ clientCapabilities: capabilities
1579
+ }),
1580
+ limits.requestTimeout,
1581
+ options.signal
1582
+ );
1583
+ if (initialized.protocolVersion !== PROTOCOL_VERSION)
1584
+ throw new ACPFailure("unsupported");
1585
+ const agentCapabilities = initialized.agentCapabilities ?? {};
1586
+ let activePrompt;
1587
+ async function prompt(content, signal) {
1588
+ if (activePrompt)
1589
+ throw new ACPFailure("busy");
1590
+ if (!sessionId)
1591
+ throw new ACPFailure("missing");
1592
+ if (Buffer.byteLength(JSON.stringify(content)) > limits.maxContentBytes)
1593
+ throw new ACPFailure("output");
1594
+ for (const item of content) {
1595
+ if (item.type === "image" && !agentCapabilities.promptCapabilities?.image || item.type === "audio" && !agentCapabilities.promptCapabilities?.audio || item.type === "resource" && !agentCapabilities.promptCapabilities?.embeddedContext) {
1596
+ throw new ACPFailure("unsupported");
1597
+ }
1598
+ }
1599
+ const events = [];
1600
+ let bytes = 0;
1601
+ let text = "";
1602
+ let messageId;
1603
+ const listener = (event) => {
1604
+ bytes += Buffer.byteLength(JSON.stringify(event));
1605
+ if (events.length >= limits.maxPendingEvents || bytes > limits.maxContentBytes) {
1606
+ throw new ACPFailure("output");
1607
+ }
1608
+ events.push(event);
1609
+ if (event.update.sessionUpdate === "agent_message_chunk" && event.update.content.type === "text") {
1610
+ if (event.update.messageId && event.update.messageId !== messageId)
1611
+ text = "";
1612
+ messageId = event.update.messageId;
1613
+ text += event.update.content.text;
1614
+ }
1615
+ };
1616
+ listeners.add(listener);
1617
+ activePrompt = new AbortController();
1618
+ try {
1619
+ const response = await request(
1620
+ () => connection.agent.request(methods.agent.session.prompt, {
1621
+ sessionId,
1622
+ prompt: content
1623
+ }),
1624
+ limits.promptTimeout,
1625
+ signal ? AbortSignal.any([activePrompt.signal, signal]) : activePrompt.signal,
1626
+ true,
1627
+ (output) => output.stopReason === "cancelled"
1628
+ );
1629
+ if (response.stopReason === "cancelled")
1630
+ throw new ACPFailure("cancelled");
1631
+ return { ...response, text, events };
1632
+ } finally {
1633
+ listeners.delete(listener);
1634
+ activePrompt = void 0;
1635
+ }
1636
+ }
1637
+ return {
1638
+ initialized,
1639
+ /**
1640
+ * 关闭传输并终止拥有的进程树,可重复调用。
1641
+ * @returns 清理完成的 Promise;清理失败抛错。
1642
+ * @example
1643
+ * ```ts
1644
+ * // 内部上下文:runtime/git/connection 为相应工厂已创建的句柄。
1645
+ * await connection.close()
1646
+ * ```
1647
+ */
1648
+ close,
1649
+ /**
1650
+ * 执行单轮原生请求并收集有限事件。
1651
+ * @param content - 原生内容块。
1652
+ * @param signal - 本轮取消信号。
1653
+ * @returns 最终文本、停止原因及事件;业务边界负责转换异常。
1654
+ * @example
1655
+ * ```ts
1656
+ * // 内部上下文:runtime/git/connection 为相应工厂已创建的句柄。
1657
+ * const response = await connection.prompt([{ type: 'text', text: '检查工程' }])
1658
+ * ```
1659
+ */
1660
+ prompt,
1661
+ /**
1662
+ * 订阅会话事件;返回取消订阅函数。
1663
+ * @param listener - 消费原生事件的回调;抛错会关闭连接。
1664
+ * @returns 取消订阅函数。
1665
+ * @example
1666
+ * ```ts
1667
+ * // 模块内部,connection 为 connectACP 返回值,异常由业务边界转换。
1668
+ * const unsubscribe = connection.subscribe(event => consume(event)); unsubscribe()
1669
+ * ```
1670
+ */
1671
+ subscribe(listener) {
1672
+ listeners.add(listener);
1673
+ return () => listeners.delete(listener);
1674
+ },
1675
+ /**
1676
+ * 发送原生取消通知并触发正在执行轮次的取消期限。
1677
+ * @returns 通知发送后的 Promise;本轮终止由 prompt 的取消期限保证。
1678
+ * @example
1679
+ * ```ts
1680
+ * // 模块内部,connection 为 connectACP 返回值,异常由业务边界转换。
1681
+ * await connection.cancel()
1682
+ * ```
1683
+ */
1684
+ cancel: async () => {
1685
+ activePrompt?.abort();
1686
+ if (sessionId && !closed) {
1687
+ await connection.agent.notify(methods.agent.session.cancel, {
1688
+ sessionId
1689
+ });
1690
+ }
1691
+ },
1692
+ /**
1693
+ * 选择握手 authMethods 中的认证方法,凭据由原生进程或远程服务提供。
1694
+ * @param methodId - 对端握手声明的认证方法。
1695
+ * @returns 原生认证响应。
1696
+ * @example
1697
+ * ```ts
1698
+ * // 模块内部,connection 为 connectACP 返回值,异常由业务边界转换。
1699
+ * await connection.authenticate('api-key')
1700
+ * ```
1701
+ */
1702
+ authenticate: (methodId) => request(
1703
+ () => connection.agent.request(methods.agent.authenticate, { methodId })
1704
+ ),
1705
+ /**
1706
+ * 创建此连接唯一的原生会话;MCP 类型必须由智能体声明支持。
1707
+ * @param cwd - 对端可见的绝对工作目录。
1708
+ * @param mcpServers - 原生 MCP 定义,默认空列表。
1709
+ * @returns 包含原生 sessionId 的新建响应。
1710
+ * @example
1711
+ * ```ts
1712
+ * // 模块内部,connection 为 connectACP 返回值,异常由业务边界转换。
1713
+ * await connection.createSession('/srv/project', [])
1714
+ * ```
1715
+ */
1716
+ async createSession(cwd, mcpServers = []) {
1717
+ if (sessionId || sessionOpening)
1718
+ throw new ACPFailure("busy");
1719
+ sessionOpening = true;
1720
+ try {
1721
+ checkMcp2(mcpServers);
1722
+ const response = await request(
1723
+ () => connection.agent.request(methods.agent.session.new, {
1724
+ cwd,
1725
+ mcpServers
1726
+ })
1727
+ );
1728
+ sessionId = response.sessionId;
1729
+ return response;
1730
+ } finally {
1731
+ sessionOpening = false;
1732
+ }
1733
+ },
1734
+ /**
1735
+ * 优先调用原生 resume,否则使用已声明的 load;均不支持则失败。
1736
+ * @param id - 原生会话 ID。
1737
+ * @param cwd - 原会话绝对工作目录。
1738
+ * @param mcpServers - 当前原生 MCP 定义。
1739
+ * @returns 恢复或加载响应;能力缺失时抛错。
1740
+ * @example
1741
+ * ```ts
1742
+ * // 模块内部,connection 为 connectACP 返回值,异常由业务边界转换。
1743
+ * await connection.resumeSession('native-id', '/srv/project')
1744
+ * ```
1745
+ */
1746
+ async resumeSession(id, cwd, mcpServers = []) {
1747
+ if (sessionId || sessionOpening)
1748
+ throw new ACPFailure("busy");
1749
+ checkMcp2(mcpServers);
1750
+ sessionId = id;
1751
+ if (agentCapabilities.sessionCapabilities?.resume) {
1752
+ return request(
1753
+ () => connection.agent.request(methods.agent.session.resume, {
1754
+ sessionId: id,
1755
+ cwd,
1756
+ mcpServers
1757
+ })
1758
+ );
1759
+ }
1760
+ if (agentCapabilities.loadSession) {
1761
+ return request(
1762
+ () => connection.agent.request(methods.agent.session.load, {
1763
+ sessionId: id,
1764
+ cwd,
1765
+ mcpServers
1766
+ })
1767
+ );
1768
+ }
1769
+ throw new ACPFailure("unsupported");
1770
+ },
1771
+ /**
1772
+ * 按智能体原生游标列出会话;未声明 list 能力则失败。
1773
+ * @param cursor - 可选分页游标。
1774
+ * @returns 原生分页会话列表。
1775
+ * @example
1776
+ * ```ts
1777
+ * // 模块内部,connection 为 connectACP 返回值,异常由业务边界转换。
1778
+ * const page = await connection.listSessions()
1779
+ * ```
1780
+ */
1781
+ async listSessions(cursor) {
1782
+ if (!agentCapabilities.sessionCapabilities?.list)
1783
+ throw new ACPFailure("unsupported");
1784
+ return request(
1785
+ () => connection.agent.request(methods.agent.session.list, { cursor })
1786
+ );
1787
+ },
1788
+ /**
1789
+ * 要求远端删除指定原生会话;不代替框架工作区清理。
1790
+ * @param id - 原生会话 ID。
1791
+ * @returns 原生删除响应。
1792
+ * @example
1793
+ * ```ts
1794
+ * // 模块内部,connection 为 connectACP 返回值,异常由业务边界转换。
1795
+ * await connection.deleteSession('native-id')
1796
+ * ```
1797
+ */
1798
+ async deleteSession(id) {
1799
+ if (!agentCapabilities.sessionCapabilities?.delete)
1800
+ throw new ACPFailure("unsupported");
1801
+ return request(
1802
+ () => connection.agent.request(methods.agent.session.delete, {
1803
+ sessionId: id
1804
+ })
1805
+ );
1806
+ },
1807
+ /**
1808
+ * 若远端声明 close 能力则关闭原生会话;进程停止由 close 负责。
1809
+ * @returns 原生关闭完成的 Promise;未声明能力时无需请求。
1810
+ * @example
1811
+ * ```ts
1812
+ * // 模块内部,connection 为 connectACP 返回值,异常由业务边界转换。
1813
+ * await connection.closeSession()
1814
+ * ```
1815
+ */
1816
+ async closeSession() {
1817
+ if (!closed && sessionId && agentCapabilities.sessionCapabilities?.close) {
1818
+ await request(
1819
+ () => connection.agent.request(methods.agent.session.close, {
1820
+ sessionId
1821
+ })
1822
+ );
1823
+ }
1824
+ },
1825
+ /**
1826
+ * 给当前原生会话设置智能体公布的模式。
1827
+ * @param modeId - 原生模式 ID。
1828
+ * @returns 原生模式响应。
1829
+ * @example
1830
+ * ```ts
1831
+ * // 模块内部,connection 为 connectACP 返回值,异常由业务边界转换。
1832
+ * await connection.setMode('code')
1833
+ * ```
1834
+ */
1835
+ setMode: (modeId) => request(
1836
+ () => connection.agent.request(methods.agent.session.setMode, {
1837
+ sessionId,
1838
+ modeId
1839
+ })
1840
+ ),
1841
+ /**
1842
+ * 给当前原生会话设置智能体公布的配置选项。
1843
+ * @param configId - 原生配置选项 ID。
1844
+ * @param value - 智能体允许的选项值。
1845
+ * @returns 原生配置响应。
1846
+ * @example
1847
+ * ```ts
1848
+ * // 模块内部,connection 为 connectACP 返回值,异常由业务边界转换。
1849
+ * await connection.setConfigOption('model', 'coding')
1850
+ * ```
1851
+ */
1852
+ setConfigOption: (configId, value) => request(
1853
+ () => connection.agent.request(methods.agent.session.setConfigOption, {
1854
+ sessionId,
1855
+ configId,
1856
+ value
1857
+ })
1858
+ )
1859
+ };
1860
+ } catch (error) {
1861
+ await close();
1862
+ throw error;
1863
+ }
1864
+ }
1865
+
1866
+ // src/acp/ai-acp-git.ts
1867
+ function createGitOperations(workspace, runtime) {
1868
+ const git = (cwd, args, env) => runtime.run("git", ["-C", cwd, ...args], void 0, {
1869
+ GIT_TERMINAL_PROMPT: "0",
1870
+ ...env
1871
+ });
1872
+ const source = (id) => runtime.path(`main/${id}`);
1873
+ async function ready() {
1874
+ if (!workspace.repositories.length)
1875
+ return;
1876
+ try {
1877
+ await runtime.run("git", ["--version"]);
1878
+ } catch {
1879
+ throw new ACPFailure("gitMissing");
1880
+ }
1881
+ }
1882
+ async function clean(path) {
1883
+ if (await git(path, ["status", "--porcelain"]))
1884
+ throw new ACPFailure("conflict");
1885
+ }
1886
+ async function supportedContent(path) {
1887
+ const index = await git(path, ["ls-files", "--stage", "-z"]);
1888
+ if (index.split("\0").some((entry) => entry.startsWith("160000 ")))
1889
+ throw new ACPFailure("unsupported");
1890
+ const attributes = await git(path, ["ls-files", "-z", "--", ".gitattributes", ":(glob)**/.gitattributes"]);
1891
+ for (const file of attributes.split("\0").filter(Boolean)) {
1892
+ const content = await git(path, ["show", `HEAD:${file}`]);
1893
+ if (content.split("\n").some((line) => !line.trimStart().startsWith("#") && /\bfilter=lfs\b/.test(line)))
1894
+ throw new ACPFailure("unsupported");
1895
+ }
1896
+ }
1897
+ async function checked(repo, path = source(repo.id), branch = repo.mainBranch) {
1898
+ if (await git(path, ["branch", "--show-current"]) !== branch)
1899
+ throw new ACPFailure("conflict");
1900
+ if (await git(path, ["remote", "get-url", repo.remote]) !== repo.url)
1901
+ throw new ACPFailure("conflict");
1902
+ await runtime.file("exists", path);
1903
+ }
1904
+ async function reports(operation) {
1905
+ await ready();
1906
+ const output = [];
1907
+ for (const repo of workspace.repositories) {
1908
+ const report = {
1909
+ repositoryId: repo.id,
1910
+ success: false,
1911
+ phase: "check"
1912
+ };
1913
+ try {
1914
+ await operation(repo, report);
1915
+ report.success = true;
1916
+ report.phase = "done";
1917
+ } catch (error) {
1918
+ report.error = error instanceof ACPFailure ? error.kind : "failed";
1919
+ }
1920
+ output.push(report);
1921
+ }
1922
+ if (output.some((item) => !item.success))
1923
+ throw new ACPFailure("partial", { reports: output });
1924
+ return output;
1925
+ }
1926
+ return {
1927
+ /**
1928
+ * 准备工程主目录,只克隆缺失仓库,不覆盖已有内容。
1929
+ * @returns main 的绝对路径;失败保留已创建文件。
1930
+ * @example
1931
+ * ```ts
1932
+ * // 内部上下文:runtime/git/connection 为相应工厂已创建的句柄。
1933
+ * const main = await git.prepare()
1934
+ * ```
1935
+ */
1936
+ async prepare() {
1937
+ await ready();
1938
+ await runtime.file("mkdir", "main/files");
1939
+ for (const repo of workspace.repositories) {
1940
+ await git(runtime.root, [
1941
+ "check-ref-format",
1942
+ "--branch",
1943
+ repo.mainBranch
1944
+ ]);
1945
+ if (await runtime.file("exists", `main/${repo.id}`) === "false") {
1946
+ await git(runtime.root, [
1947
+ "clone",
1948
+ "--origin",
1949
+ repo.remote,
1950
+ "--branch",
1951
+ repo.mainBranch,
1952
+ "--",
1953
+ repo.url,
1954
+ source(repo.id)
1955
+ ]);
1956
+ }
1957
+ await checked(repo);
1958
+ await supportedContent(source(repo.id));
1959
+ }
1960
+ return runtime.path("main");
1961
+ },
1962
+ /**
1963
+ * 从每个工程本地主分支创建独立工作树并复制普通文件。
1964
+ * @param sessionId - 已校验的框架会话 ID。
1965
+ * @param branch - 所有工程使用的同名新分支。
1966
+ * @returns 独立会话目录;中途失败不回滚已有工作树。
1967
+ * @example
1968
+ * ```ts
1969
+ * // 内部上下文:runtime/git/connection 为相应工厂已创建的句柄。
1970
+ * const directory = await git.createWorktree('task-1', 'acp/task-1')
1971
+ * ```
1972
+ */
1973
+ async createWorktree(sessionId, branch) {
1974
+ await ready();
1975
+ await runtime.file("mkdir", `sessions/${sessionId}`);
1976
+ for (const repo of workspace.repositories) {
1977
+ await checked(repo);
1978
+ await supportedContent(source(repo.id));
1979
+ await git(source(repo.id), ["check-ref-format", "--branch", branch]);
1980
+ await git(source(repo.id), [
1981
+ "worktree",
1982
+ "add",
1983
+ "-b",
1984
+ branch,
1985
+ runtime.path(`sessions/${sessionId}/${repo.id}`),
1986
+ `refs/heads/${repo.mainBranch}`
1987
+ ]);
1988
+ }
1989
+ await runtime.file("copy", "main/files", `sessions/${sessionId}/files`);
1990
+ return runtime.path(`sessions/${sessionId}`);
1991
+ },
1992
+ /**
1993
+ * 更新各工程主分支,只允许快进合并。
1994
+ * @returns 每个工程的更新报告;部分失败抛出携带 reports 的异常。
1995
+ * @example
1996
+ * ```ts
1997
+ * // 内部上下文:runtime/git/connection 为相应工厂已创建的句柄。
1998
+ * const reports = await git.updateMain()
1999
+ * ```
2000
+ */
2001
+ updateMain: () => reports(async (repo, report) => {
2002
+ const path = source(repo.id);
2003
+ await checked(repo);
2004
+ await clean(path);
2005
+ report.phase = "fetch";
2006
+ await git(path, [
2007
+ "fetch",
2008
+ "--",
2009
+ repo.remote,
2010
+ `refs/heads/${repo.mainBranch}`
2011
+ ]);
2012
+ report.phase = "merge";
2013
+ await git(path, ["merge", "--ff-only", "FETCH_HEAD"]);
2014
+ report.commit = await git(path, ["rev-parse", "HEAD"]);
2015
+ }),
2016
+ /**
2017
+ * 逐仓提交会话修改并发布,禁止强推。
2018
+ * @param sessionId - 已登记的独立会话 ID。
2019
+ * @param branch - 会话工作树分支名。
2020
+ * @param request - 经过 schema 校验的模式、提交说明及作者。
2021
+ * @returns 各仓发布报告;部分失败保留已提交和已推送的结果。
2022
+ * @example
2023
+ * ```ts
2024
+ * // 内部上下文:runtime/git/connection 为相应工厂已创建的句柄。
2025
+ * const reports = await git.publish('task-1', 'acp/task-1', { mode: 'push-branch', message: '完成任务', author: { name: 'Developer', email: 'dev@example.com' } })
2026
+ * ```
2027
+ */
2028
+ publish: (sessionId, branch, request) => reports(async (repo, report) => {
2029
+ const session = runtime.path(`sessions/${sessionId}/${repo.id}`);
2030
+ const main = source(repo.id);
2031
+ const author = {
2032
+ GIT_AUTHOR_NAME: request.author.name,
2033
+ GIT_AUTHOR_EMAIL: request.author.email,
2034
+ GIT_COMMITTER_NAME: request.author.name,
2035
+ GIT_COMMITTER_EMAIL: request.author.email
2036
+ };
2037
+ await checked(repo, session, branch);
2038
+ if (request.mode === "merge-main") {
2039
+ await checked(repo);
2040
+ await clean(main);
2041
+ }
2042
+ report.phase = "commit";
2043
+ if (await git(session, ["status", "--porcelain"])) {
2044
+ await git(session, ["add", "--all"]);
2045
+ await git(session, ["commit", "-m", request.message], author);
2046
+ }
2047
+ report.commit = await git(session, ["rev-parse", "HEAD"]);
2048
+ let target = session;
2049
+ let targetBranch = branch;
2050
+ if (request.mode === "merge-main") {
2051
+ report.phase = "fetch";
2052
+ await git(main, [
2053
+ "fetch",
2054
+ "--",
2055
+ repo.remote,
2056
+ `refs/heads/${repo.mainBranch}`
2057
+ ]);
2058
+ report.phase = "merge";
2059
+ const remoteHead = await git(main, ["rev-parse", "FETCH_HEAD"]);
2060
+ const localHead = await git(main, ["rev-parse", "HEAD"]);
2061
+ const base = await git(main, ["merge-base", "HEAD", "FETCH_HEAD"]);
2062
+ if (base === localHead)
2063
+ await git(main, ["merge", "--ff-only", "FETCH_HEAD"]);
2064
+ else if (base !== remoteHead)
2065
+ throw new ACPFailure("conflict");
2066
+ await git(main, ["merge", "--no-edit", "--", branch], author);
2067
+ target = main;
2068
+ targetBranch = repo.mainBranch;
2069
+ }
2070
+ report.phase = "push";
2071
+ await git(target, [
2072
+ "push",
2073
+ "--",
2074
+ repo.remote,
2075
+ `HEAD:refs/heads/${targetBranch}`
2076
+ ]);
2077
+ report.commit = await git(target, ["rev-parse", "HEAD"]);
2078
+ })
2079
+ };
2080
+ }
2081
+ var fileProgram = String.raw`
2082
+ const fs = require('node:fs/promises'); const path = require('node:path');
2083
+ (async () => {
2084
+ const [root, op, name, content] = process.argv.slice(1);
2085
+ const base = await fs.realpath(root);
2086
+ const target = path.resolve(base, name || '.');
2087
+ const rel = path.relative(base, target);
2088
+ if (rel.startsWith('..') || path.isAbsolute(rel)) throw Error('scope');
2089
+ let cursor = base;
2090
+ for (const part of rel.split(path.sep).filter(Boolean)) {
2091
+ cursor = path.join(cursor, part);
2092
+ try { if ((await fs.lstat(cursor)).isSymbolicLink()) throw Error('link'); }
2093
+ catch (e) { if (e.code !== 'ENOENT') throw e; }
2094
+ }
2095
+ if (op === 'mkdir') await fs.mkdir(target, { recursive: true });
2096
+ else if (op === 'exists') process.stdout.write(String(await fs.stat(target).then(() => true, e => { if(e.code === 'ENOENT') return false; throw e; })));
2097
+ else if (op === 'write') { await fs.mkdir(path.dirname(target), { recursive: true }); await fs.writeFile(target, Buffer.from(content, 'base64'), { flag: 'wx', mode: 0o600 }); }
2098
+ else if (op === 'read') process.stdout.write((await fs.readFile(target)).toString('base64'));
2099
+ else if (op === 'copy') {
2100
+ const destination = path.resolve(base, content);
2101
+ const destRel = path.relative(base, destination);
2102
+ if(destRel.startsWith('..') || path.isAbsolute(destRel)) throw Error('scope');
2103
+ let destCursor = base;
2104
+ for (const part of destRel.split(path.sep).filter(Boolean)) {
2105
+ destCursor = path.join(destCursor, part);
2106
+ try { if ((await fs.lstat(destCursor)).isSymbolicLink()) throw Error('link'); }
2107
+ catch(e) { if(e.code !== 'ENOENT') throw e; }
2108
+ }
2109
+ async function copy(src, dst) {
2110
+ try { if ((await fs.lstat(dst)).isSymbolicLink()) throw Error('link'); }
2111
+ catch(e) { if(e.code !== 'ENOENT') throw e; }
2112
+ const stat = await fs.lstat(src); if(stat.isSymbolicLink()) throw Error('link');
2113
+ if(stat.isDirectory()) { await fs.mkdir(dst, { recursive: true }); for(const item of await fs.readdir(src)) await copy(path.join(src,item),path.join(dst,item)); }
2114
+ else if(stat.isFile()) await fs.copyFile(src,dst,require('node:fs').constants.COPYFILE_EXCL);
2115
+ else throw Error('special file');
2116
+ }
2117
+ await copy(target,destination);
2118
+ }
2119
+ else throw Error('operation');
2120
+ })().catch(() => { process.exitCode = 1 });`;
2121
+ async function selectEngine(preference) {
2122
+ for (const engine of preference === "auto" ? ["podman", "docker"] : [preference]) {
2123
+ try {
2124
+ await execute(engine, ["info"], void 0, void 0, 15e3);
2125
+ return engine;
2126
+ } catch {
2127
+ if (preference !== "auto")
2128
+ throw new ACPFailure("runtime", { engine });
2129
+ }
2130
+ }
2131
+ throw new ACPFailure("runtime");
2132
+ }
2133
+ async function openRuntime(workspace) {
2134
+ const environment = {};
2135
+ for (const [target, source] of Object.entries(workspace.environmentRefs)) {
2136
+ if (!process2.env[source])
2137
+ throw new ACPFailure("invalid", { environment: source });
2138
+ environment[target] = process2.env[source];
2139
+ }
2140
+ let root;
2141
+ let container;
2142
+ let engine;
2143
+ let closed = false;
2144
+ if (workspace.storage.type === "directory") {
2145
+ await mkdir(workspace.storage.path, { recursive: true });
2146
+ root = await realpath(resolve(workspace.storage.path));
2147
+ } else {
2148
+ engine = await selectEngine(workspace.storage.engine);
2149
+ root = "/workspace";
2150
+ container = await execute(engine, [
2151
+ "create",
2152
+ // 远程 Podman 会从服务端继承代理,绕过最小环境白名单;只允许显式 environmentRefs 注入。
2153
+ ...engine === "podman" ? ["--http-proxy=false"] : [],
2154
+ "--init",
2155
+ "--mount",
2156
+ `type=volume,src=${workspace.storage.name},dst=/workspace`,
2157
+ "--mount",
2158
+ `type=volume,src=${workspace.storage.name}-state,dst=/state`,
2159
+ "--workdir",
2160
+ root,
2161
+ "--entrypoint",
2162
+ "node",
2163
+ workspace.storage.image,
2164
+ "-e",
2165
+ "setInterval(() => {}, 2147483647)"
2166
+ ]);
2167
+ if (!/^[a-f0-9]{12,64}$/.test(container))
2168
+ throw new ACPFailure("runtime");
2169
+ try {
2170
+ await execute(engine, ["start", container]);
2171
+ } catch (error) {
2172
+ await execute(engine, ["rm", "-f", container]);
2173
+ throw error;
2174
+ }
2175
+ }
2176
+ const ensure = () => {
2177
+ if (closed)
2178
+ throw new ACPFailure("closed");
2179
+ };
2180
+ const path = (relative2) => workspace.storage.type === "volume" ? `${root}/${relative2}` : resolve(root, relative2);
2181
+ function transport(command, args, cwd, env = {}) {
2182
+ ensure();
2183
+ const values = { ...environment, ...env };
2184
+ return engine && container ? {
2185
+ type: "stdio",
2186
+ command: engine,
2187
+ args: [
2188
+ "exec",
2189
+ "-i",
2190
+ "--workdir",
2191
+ cwd,
2192
+ ...Object.keys(values).flatMap((key) => [
2193
+ "--env",
2194
+ Object.hasOwn(engineEnvironment(), key) ? `${key}=${values[key]}` : key
2195
+ ]),
2196
+ container,
2197
+ command,
2198
+ ...args
2199
+ ],
2200
+ env: { ...values, ...engineEnvironment() }
2201
+ } : { type: "stdio", command, args, cwd, env: values };
2202
+ }
2203
+ async function run(command, args, cwd = root, env) {
2204
+ const launch = transport(command, args, cwd, env);
2205
+ if (launch.type !== "stdio")
2206
+ throw new ACPFailure("runtime");
2207
+ return execute(launch.command, launch.args ?? [], launch.cwd, launch.env);
2208
+ }
2209
+ return {
2210
+ root,
2211
+ path,
2212
+ transport,
2213
+ run,
2214
+ resource: container ? { engine, container } : void 0,
2215
+ /**
2216
+ * 计算受管理的会话状态目录。
2217
+ * @param sessionId - 已校验的会话 ID。
2218
+ * @returns 状态目录绝对路径;不创建目录。
2219
+ * @example
2220
+ * ```ts
2221
+ * // 内部上下文:runtime/git/connection 为相应工厂已创建的句柄。
2222
+ * const state = runtime.state('task-1')
2223
+ * ```
2224
+ */
2225
+ state: (sessionId) => workspace.storage.type === "volume" ? `/state/${sessionId}` : path(`.state/${sessionId}`),
2226
+ /**
2227
+ * 执行受工作区约束的文件操作,拒绝链接和路径逃逸。
2228
+ * @param op - mkdir/exists/write/read/copy 操作。
2229
+ * @param name - 工作区内相对路径或已校验的内部绝对路径。
2230
+ * @param content - write 时为 Base64,copy 时为目标路径,其余为空。
2231
+ * @returns 文本结果:exists 为 true/false,read 为 Base64,其余为空。
2232
+ * @example
2233
+ * ```ts
2234
+ * // 内部上下文:runtime/git/connection 为相应工厂已创建的句柄。
2235
+ * await runtime.file('mkdir', 'main/files')
2236
+ * ```
2237
+ */
2238
+ async file(op, name, content = "") {
2239
+ return run(
2240
+ workspace.storage.type === "volume" ? "node" : process2.execPath,
2241
+ ["-e", fileProgram, root, op, name, content]
2242
+ );
2243
+ },
2244
+ /**
2245
+ * 向已创建的独立状态目录写入配置,拒绝符号链接。
2246
+ * @param sessionId - 已校验的会话 ID。
2247
+ * @param name - 无目录分隔符的配置文件名。
2248
+ * @param content - 原生配置文本,可能含凭据,不应记录日志。
2249
+ * @returns 完成写入的 Promise。
2250
+ * @example
2251
+ * ```ts
2252
+ * // 内部上下文:runtime/git/connection 为相应工厂已创建的句柄。
2253
+ * await runtime.writeState('task-1', 'config.json', '{}')
2254
+ * ```
2255
+ */
2256
+ async writeState(sessionId, name, content) {
2257
+ if (!/^[\w-]+\.[a-z0-9]+$/i.test(name))
2258
+ throw new ACPFailure("invalid");
2259
+ const directory = workspace.storage.type === "volume" ? `/state/${sessionId}` : path(`.state/${sessionId}`);
2260
+ await run(
2261
+ workspace.storage.type === "volume" ? "node" : process2.execPath,
2262
+ [
2263
+ "-e",
2264
+ 'const f=require("node:fs"),p=require("node:path");const d=process.argv[1];let c=p.parse(d).root;for(const part of d.slice(c.length).split(p.sep).filter(Boolean)){c=p.join(c,part);if(f.lstatSync(c).isSymbolicLink())process.exit(1)}const t=p.join(d,process.argv[2]);try{if(f.lstatSync(t).isSymbolicLink())process.exit(1)}catch(e){if(e.code!=="ENOENT")throw e}f.writeFileSync(t,process.argv[3],{mode:384})',
2265
+ directory,
2266
+ name,
2267
+ content
2268
+ ]
2269
+ );
2270
+ },
2271
+ /**
2272
+ * 创建私有状态目录,拒绝祖先目录中的符号链接。
2273
+ * @param sessionId - 已校验的会话 ID。
2274
+ * @returns 已创建的状态目录绝对路径。
2275
+ * @example
2276
+ * ```ts
2277
+ * // 内部上下文:runtime/git/connection 为相应工厂已创建的句柄。
2278
+ * const directory = await runtime.stateDirectory('task-1')
2279
+ * ```
2280
+ */
2281
+ async stateDirectory(sessionId) {
2282
+ const target = workspace.storage.type === "volume" ? `/state/${sessionId}` : path(`.state/${sessionId}`);
2283
+ await run(
2284
+ workspace.storage.type === "volume" ? "node" : process2.execPath,
2285
+ [
2286
+ "-e",
2287
+ 'const f=require("node:fs"),p=require("node:path"),d=process.argv[1];let c=p.parse(d).root;for(const part of d.slice(c.length).split(p.sep).filter(Boolean)){c=p.join(c,part);try{if(f.lstatSync(c).isSymbolicLink())process.exit(1)}catch(e){if(e.code!=="ENOENT")throw e;f.mkdirSync(c,{mode:448})}}',
2288
+ target
2289
+ ]
2290
+ );
2291
+ return target;
2292
+ },
2293
+ /**
2294
+ * 关闭本次拥有的容器;目录和 volume 保留。
2295
+ * @returns 清理完成的 Promise;失败抛错,不能视为资源已释放。
2296
+ * @example
2297
+ * ```ts
2298
+ * // 内部上下文:runtime/git/connection 为相应工厂已创建的句柄。
2299
+ * await runtime.close()
2300
+ * ```
2301
+ */
2302
+ async close() {
2303
+ if (closed)
2304
+ return;
2305
+ if (engine && container) {
2306
+ try {
2307
+ await execute(engine, ["rm", "--force", container]);
2308
+ } catch {
2309
+ throw new ACPFailure("cleanup", { engine, container });
2310
+ }
2311
+ }
2312
+ closed = true;
2313
+ }
2314
+ };
2315
+ }
2316
+
2317
+ // src/acp/ai-acp-functions.ts
2318
+ function createACPOperations(provider, config, extensions = {}, modelConfig = async () => config, mcpLookup) {
2319
+ const workspaces = provider.createRelStore(
2320
+ "hai_ai_acp_workspaces"
2321
+ );
2322
+ const agents = provider.createRelStore("hai_ai_acp_agents");
2323
+ const sessions = provider.createRelStore(
2324
+ "hai_ai_acp_sessions"
2325
+ );
2326
+ const locks = provider.createRelStore(
2327
+ "hai_ai_acp_locks"
2328
+ );
2329
+ const adapters = { ...nativeAdapters, ...extensions };
2330
+ const live = /* @__PURE__ */ new Map();
2331
+ const connections = /* @__PURE__ */ new Set();
2332
+ const limits = ACPConfigSchema.parse(config.acp ?? {});
2333
+ const pending = /* @__PURE__ */ new Set();
2334
+ const stopping = /* @__PURE__ */ new Map();
2335
+ let closed = false;
2336
+ const agentRegistry = createResourceRegistry(
2337
+ agents,
2338
+ ACPAgentSchema,
2339
+ config.acp?.agents ?? [],
2340
+ () => !closed
2341
+ );
2342
+ const workspaceRegistry = createResourceRegistry(
2343
+ workspaces,
2344
+ ACPWorkspaceSchema,
2345
+ config.acp?.workspaces ?? [],
2346
+ () => !closed
2347
+ );
2348
+ function ensure() {
2349
+ if (closed)
2350
+ throw new ACPFailure("closed");
2351
+ }
2352
+ async function protect(operation) {
2353
+ const task = (async () => {
2354
+ try {
2355
+ ensure();
2356
+ return ok(await operation());
2357
+ } catch (error) {
2358
+ return acpError(error);
2359
+ }
2360
+ })();
2361
+ pending.add(task);
2362
+ try {
2363
+ return await task;
2364
+ } finally {
2365
+ pending.delete(task);
2366
+ }
2367
+ }
2368
+ async function modelSignature(agent) {
2369
+ return createHash("sha256").update(
2370
+ JSON.stringify(
2371
+ resolveAgentModels(agent, await modelConfig()).map(
2372
+ ({ id, config: model }) => ({
2373
+ id,
2374
+ model: model.model,
2375
+ protocol: model.protocol,
2376
+ baseUrl: model.baseUrl,
2377
+ capabilities: model.capabilities
2378
+ })
2379
+ )
2380
+ )
2381
+ ).digest("hex");
2382
+ }
2383
+ async function protectResult(operation) {
2384
+ const result = await protect(operation);
2385
+ return result.success ? result.data : result;
2386
+ }
2387
+ async function getAgent(id) {
2388
+ const entry = await agentRegistry.read(id);
2389
+ if (!entry)
2390
+ throw new ACPFailure("missing", { id });
2391
+ return entry.value;
2392
+ }
2393
+ async function getWorkspace(id) {
2394
+ const entry = await workspaceRegistry.read(id);
2395
+ if (!entry)
2396
+ throw new ACPFailure("missing", { id });
2397
+ const workspace = await normalizeWorkspace(entry.value);
2398
+ await checkWorkspaceOverlap(workspace);
2399
+ return workspace;
2400
+ }
2401
+ const workspaceKey = (workspace) => workspace.storage.type === "directory" ? `directory:${process2.platform === "win32" ? workspace.storage.path.toLowerCase() : workspace.storage.path}` : `volume:${workspace.storage.engine}:${workspace.storage.name}`;
2402
+ async function normalizeWorkspace(workspace) {
2403
+ if (workspace.storage.type === "directory") {
2404
+ await mkdir(workspace.storage.path, { recursive: true });
2405
+ workspace.storage.path = await realpath(resolve(workspace.storage.path));
2406
+ } else {
2407
+ workspace.storage.engine = await selectEngine(workspace.storage.engine);
2408
+ }
2409
+ return workspace;
2410
+ }
2411
+ async function canonicalDirectory(path) {
2412
+ const target = resolve(path);
2413
+ try {
2414
+ return await realpath(target);
2415
+ } catch (error) {
2416
+ if (error.code !== "ENOENT" || dirname(target) === target) {
2417
+ throw error;
2418
+ }
2419
+ return resolve(
2420
+ await canonicalDirectory(dirname(target)),
2421
+ basename(target)
2422
+ );
2423
+ }
2424
+ }
2425
+ async function checkWorkspaceOverlap(workspace) {
2426
+ for (const entry of await workspaceRegistry.values()) {
2427
+ if (entry.id === workspace.id)
2428
+ continue;
2429
+ if (entry.storage.type === "directory" && workspace.storage.type === "directory") {
2430
+ const existing = await canonicalDirectory(entry.storage.path);
2431
+ const child = relative(existing, workspace.storage.path);
2432
+ const parent = relative(workspace.storage.path, existing);
2433
+ if (!child.startsWith("..") && !isAbsolute(child) || !parent.startsWith("..") && !isAbsolute(parent)) {
2434
+ throw new ACPFailure("conflict");
2435
+ }
2436
+ } else if (entry.storage.type === "volume" && workspace.storage.type === "volume" && entry.storage.name === workspace.storage.name && (entry.storage.engine === "auto" || entry.storage.engine === workspace.storage.engine)) {
2437
+ throw new ACPFailure("conflict");
2438
+ }
2439
+ }
2440
+ }
2441
+ async function get(store, id) {
2442
+ sessionIdentitySchema.parse(id);
2443
+ const data = await store.get(id);
2444
+ if (!data)
2445
+ throw new ACPFailure("missing", { id });
2446
+ return data;
2447
+ }
2448
+ async function acquire(key) {
2449
+ const previous = await locks.readVersioned(key);
2450
+ if (previous?.data.owner)
2451
+ throw new ACPFailure("busy");
2452
+ const owner = randomUUID();
2453
+ const version = await locks.compareAndSwap(key, {
2454
+ expectedVersion: previous?.version,
2455
+ data: { owner }
2456
+ });
2457
+ if (!version)
2458
+ throw new ACPFailure("busy");
2459
+ return async () => {
2460
+ if (!await locks.compareAndSwap(key, {
2461
+ expectedVersion: version,
2462
+ data: { owner: null }
2463
+ })) {
2464
+ throw new ACPFailure("cleanup");
2465
+ }
2466
+ };
2467
+ }
2468
+ async function withDefinitions(operation) {
2469
+ const release = await acquire("definition-registry");
2470
+ try {
2471
+ return await operation();
2472
+ } finally {
2473
+ await release();
2474
+ }
2475
+ }
2476
+ async function workspaceOperation(workspace, action) {
2477
+ const release = await acquire(`${workspaceKey(workspace)}:main`);
2478
+ let runtime;
2479
+ try {
2480
+ runtime = await openRuntime(workspace);
2481
+ return await action(runtime);
2482
+ } finally {
2483
+ await runtime?.close();
2484
+ await release();
2485
+ }
2486
+ }
2487
+ async function unique(store, id, data) {
2488
+ if (!await store.compareAndSwap(id, { expectedVersion: void 0, data }))
2489
+ throw new ACPFailure("conflict", { id });
2490
+ return data;
2491
+ }
2492
+ async function validateAgent(agent) {
2493
+ if (agent.mode === "custom-service")
2494
+ return;
2495
+ if (!adapters[agent.adapter])
2496
+ throw new ACPFailure("unsupported", { adapter: agent.adapter });
2497
+ resolveAgentModels(agent, await modelConfig());
2498
+ }
2499
+ async function launchAgent(agent, runtime, info) {
2500
+ if (agent.mode === "custom-service") {
2501
+ return {
2502
+ type: agent.transport,
2503
+ url: agent.url,
2504
+ headers: resolveEnvironment(agent.headerEnv)
2505
+ };
2506
+ }
2507
+ if (!runtime)
2508
+ throw new ACPFailure("missing");
2509
+ const statePath = await runtime.stateDirectory(info.id);
2510
+ const native = await adapters[agent.adapter].configure({
2511
+ agent,
2512
+ models: resolveAgentModels(agent, await modelConfig()),
2513
+ modelId: info.modelId,
2514
+ statePath
2515
+ });
2516
+ for (const [name, content] of Object.entries(native.files ?? {}))
2517
+ await runtime.writeState(info.id, name, content);
2518
+ return runtime.transport(native.command, native.args, info.cwd, {
2519
+ HOME: statePath,
2520
+ USERPROFILE: statePath,
2521
+ ...resolveEnvironment(agent.environmentRefs),
2522
+ ...native.env
2523
+ });
2524
+ }
2525
+ async function stop(id) {
2526
+ const existing = stopping.get(id);
2527
+ if (existing)
2528
+ return existing;
2529
+ const task = stopOne(id);
2530
+ stopping.set(id, task);
2531
+ try {
2532
+ await task;
2533
+ } finally {
2534
+ stopping.delete(id);
2535
+ }
2536
+ }
2537
+ async function stopOne(id) {
2538
+ const current = live.get(id);
2539
+ if (!current)
2540
+ return;
2541
+ await current.connection.cancel();
2542
+ try {
2543
+ await current.connection.closeSession();
2544
+ } catch {
2545
+ } finally {
2546
+ await current.connection.close();
2547
+ await current.runtime?.close();
2548
+ }
2549
+ current.info.status = "closed";
2550
+ current.info.resource = void 0;
2551
+ await sessions.save(id, current.info);
2552
+ await current.release();
2553
+ live.delete(id);
2554
+ }
2555
+ async function start(info, options, resume) {
2556
+ const agent = info.agent;
2557
+ const releaseSession = await acquire(`session:${info.id}`);
2558
+ let releaseDirectory;
2559
+ let runtime;
2560
+ let connection;
2561
+ let recordExists = true;
2562
+ try {
2563
+ const workspace = info.workspaceId ? await getWorkspace(info.workspaceId) : void 0;
2564
+ if (!agent.enabled)
2565
+ throw new ACPFailure("unsupported");
2566
+ if (resume && !await sessions.get(info.id)) {
2567
+ recordExists = false;
2568
+ throw new ACPFailure("missing");
2569
+ }
2570
+ if (workspace) {
2571
+ if (agent.mode === "custom-service")
2572
+ throw new ACPFailure("unsupported");
2573
+ if (agent.mode === "embedded" && workspace.storage.type !== "directory" || agent.mode === "builtin-service" && workspace.storage.type !== "volume") {
2574
+ throw new ACPFailure("unsupported");
2575
+ }
2576
+ if (agent.mode === "builtin-service" && workspace.storage.type === "volume" && (agent.image !== workspace.storage.image || agent.engine !== "auto" && agent.engine !== workspace.storage.engine)) {
2577
+ throw new ACPFailure("conflict");
2578
+ }
2579
+ if (!info.newWorktree)
2580
+ releaseDirectory = await acquire(`${workspaceKey(workspace)}:main`);
2581
+ if (!resume) {
2582
+ const releasePrepare = info.newWorktree ? await acquire(`${workspaceKey(workspace)}:main`) : void 0;
2583
+ try {
2584
+ runtime = await openRuntime(workspace);
2585
+ info.resource = runtime.resource;
2586
+ await sessions.save(info.id, info);
2587
+ const git = createGitOperations(workspace, runtime);
2588
+ await git.prepare();
2589
+ info.cwd = info.newWorktree ? await git.createWorktree(info.id, info.branch) : runtime.path("main");
2590
+ } finally {
2591
+ await releasePrepare?.();
2592
+ }
2593
+ } else {
2594
+ runtime = await openRuntime(workspace);
2595
+ }
2596
+ } else if (agent.mode !== "custom-service" || !info.cwd || info.newWorktree) {
2597
+ throw new ACPFailure("invalid");
2598
+ }
2599
+ ensure();
2600
+ const transport = await launchAgent(agent, runtime, info);
2601
+ connection = await connectACP(transport, options, limits);
2602
+ if (info.mcpServerIds?.length && !mcpLookup)
2603
+ throw new ACPFailure("unsupported");
2604
+ const mcpServers = [
2605
+ ...info.mcpServers,
2606
+ ...info.mcpServerIds?.length && mcpLookup ? await mcpLookup(info.mcpServerIds) : []
2607
+ ];
2608
+ if (resume) {
2609
+ if (!info.nativeId)
2610
+ throw new ACPFailure("unsupported");
2611
+ await connection.resumeSession(info.nativeId, info.cwd, mcpServers);
2612
+ } else {
2613
+ info.nativeId = (await connection.createSession(info.cwd, mcpServers)).sessionId;
2614
+ }
2615
+ ensure();
2616
+ info.status = "active";
2617
+ info.resource = runtime?.resource;
2618
+ await sessions.save(info.id, info);
2619
+ const value = {
2620
+ info,
2621
+ connection,
2622
+ runtime,
2623
+ release: async () => {
2624
+ await releaseDirectory?.();
2625
+ await releaseSession();
2626
+ }
2627
+ };
2628
+ live.set(info.id, value);
2629
+ return handle(info.id, value);
2630
+ } catch (error) {
2631
+ await connection?.close();
2632
+ await runtime?.close();
2633
+ info.status = "failed";
2634
+ info.resource = void 0;
2635
+ if (recordExists)
2636
+ await sessions.save(info.id, info);
2637
+ await releaseDirectory?.();
2638
+ await releaseSession();
2639
+ throw error;
2640
+ }
2641
+ }
2642
+ function handle(id, expected) {
2643
+ function current() {
2644
+ ensure();
2645
+ const value = live.get(id);
2646
+ if (!value || expected && value !== expected)
2647
+ throw new ACPFailure("closed");
2648
+ return value;
2649
+ }
2650
+ async function gitOperation(publish) {
2651
+ const request = publish ? ACPPublishSchema.parse(publish) : void 0;
2652
+ const info = await get(sessions, id);
2653
+ if (!info.workspaceId || request && !info.newWorktree)
2654
+ throw new ACPFailure("unsupported");
2655
+ const workspace = await getWorkspace(info.workspaceId);
2656
+ await stop(id);
2657
+ const release = await acquire(`session:${id}`);
2658
+ try {
2659
+ return await workspaceOperation(workspace, (runtime) => request ? createGitOperations(workspace, runtime).publish(
2660
+ id,
2661
+ info.branch,
2662
+ request
2663
+ ) : createGitOperations(workspace, runtime).updateMain());
2664
+ } finally {
2665
+ await release();
2666
+ }
2667
+ }
2668
+ return {
2669
+ id,
2670
+ /**
2671
+ * 读取持久会话信息,包括状态、原生 ID 和当前容器。
2672
+ * @returns 包含 ACPSessionInfo 的 HaiResult;记录不存在时返回失败。
2673
+ * @example
2674
+ * ```ts
2675
+ * // session 为 sessions.create/get/resume 成功返回的 data。
2676
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2677
+ * const result = await session.info()
2678
+ * ```
2679
+ */
2680
+ info: () => protect(() => get(sessions, id)),
2681
+ /**
2682
+ * 发送一轮内容,等待最终停止原因、最终消息文本和完整事件;同一会话不允许并发轮次。
2683
+ * @param content - 原生 ACP 内容块,不允许同一会话并发执行。
2684
+ * @param signal - 本轮取消信号;取消超时会关闭连接。
2685
+ * @returns 最终文本、停止原因和事件的 HaiResult。
2686
+ * @example
2687
+ * ```ts
2688
+ * // session 为 sessions.create/get/resume 成功返回的 data。
2689
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2690
+ * const result = await session.prompt([{ type: 'text', text: '检查工程' }])
2691
+ * ```
2692
+ */
2693
+ prompt: (content, signal) => protect(() => current().connection.prompt(content, signal)),
2694
+ /**
2695
+ * 仅接受正常结束的一份 JSON(允许完整 JSON 代码围栏),再按 schema 校验;前后夹杂文本视为失败。
2696
+ * @param content - 要求返回结构化 JSON 的原生内容块。
2697
+ * @param schema - 最终 JSON 的 Zod 校验器。
2698
+ * @param signal - 可选的本轮取消信号。
2699
+ * @returns 经过 schema 校验的数据;输出不合法时返回 ACP_OUTPUT_INVALID。
2700
+ * @example
2701
+ * ```ts
2702
+ * import { z } from 'zod'
2703
+ * // session 为 sessions.create/get/resume 成功返回的 data。
2704
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2705
+ * const result = await session.promptStructured([{ type: 'text', text: '返回 JSON' }], z.object({ ok: z.boolean() }))
2706
+ * ```
2707
+ */
2708
+ promptStructured: (content, schema, signal) => protect(async () => {
2709
+ const response = await current().connection.prompt(content, signal);
2710
+ if (response.stopReason !== "end_turn")
2711
+ throw new ACPFailure("output");
2712
+ let text = response.text.trim();
2713
+ const fenced = /^```(?:json)?[ \t]*\n([\s\S]*?)\n```$/.exec(text);
2714
+ if (fenced)
2715
+ text = fenced[1];
2716
+ try {
2717
+ return schema.parse(JSON.parse(text));
2718
+ } catch {
2719
+ throw new ACPFailure("output");
2720
+ }
2721
+ }),
2722
+ /**
2723
+ * 迭代本轮 ACP 事件;中途退出迭代会取消本轮,失败会抛错,调用方需 try/finally 清理。
2724
+ * @param content - 本轮原生内容块。
2725
+ * @param signal - 可选取消信号;结束迭代同样会取消本轮。
2726
+ * @returns 异步事件迭代器;流式失败抛出异常,需在调用方捕获。
2727
+ * @throws 连接关闭、取消超时、输出超限或原生请求失败。
2728
+ * @example
2729
+ * ```ts
2730
+ * // session 为 sessions.create/get/resume 成功返回的 data。
2731
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2732
+ * for await (const event of session.stream([{ type: 'text', text: '检查工程' }])) { const update = event.update }
2733
+ * ```
2734
+ */
2735
+ async *stream(content, signal) {
2736
+ const value = current();
2737
+ const controller = new AbortController();
2738
+ const queue = [];
2739
+ let wake;
2740
+ const completion = { done: false };
2741
+ let error;
2742
+ const unsubscribe = value.connection.subscribe((event) => {
2743
+ if (queue.length >= limits.maxPendingEvents)
2744
+ throw new ACPFailure("output");
2745
+ queue.push(event);
2746
+ wake?.();
2747
+ });
2748
+ const task = value.connection.prompt(
2749
+ content,
2750
+ signal ? AbortSignal.any([signal, controller.signal]) : controller.signal
2751
+ ).catch((reason) => {
2752
+ error = reason;
2753
+ }).finally(() => {
2754
+ completion.done = true;
2755
+ wake?.();
2756
+ });
2757
+ try {
2758
+ while (!completion.done || queue.length) {
2759
+ if (queue.length) {
2760
+ yield queue.shift();
2761
+ } else {
2762
+ await new Promise((resolve3) => {
2763
+ wake = resolve3;
2764
+ });
2765
+ }
2766
+ }
2767
+ if (error)
2768
+ throw error;
2769
+ } finally {
2770
+ unsubscribe();
2771
+ controller.abort();
2772
+ await task;
2773
+ }
2774
+ },
2775
+ /**
2776
+ * 请求取消正在执行的一轮;超时未停止时关闭所拥有的传输。
2777
+ * @returns 取消完成的 HaiResult;超时或清理失败返回错误。
2778
+ * @example
2779
+ * ```ts
2780
+ * // session 为 sessions.create/get/resume 成功返回的 data。
2781
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2782
+ * const result = await session.cancel()
2783
+ * ```
2784
+ */
2785
+ cancel: () => protect(() => current().connection.cancel()),
2786
+ /**
2787
+ * 选择智能体公布的原生模式 ID;不兼容时返回协议失败。
2788
+ * @param mode - 握手或事件中公布的原生模式 ID。
2789
+ * @returns 原生模式设置结果的 HaiResult。
2790
+ * @example
2791
+ * ```ts
2792
+ * // session 为 sessions.create/get/resume 成功返回的 data。
2793
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2794
+ * const result = await session.setMode('code')
2795
+ * ```
2796
+ */
2797
+ setMode: (mode) => protect(() => current().connection.setMode(mode)),
2798
+ /**
2799
+ * 设置智能体公布的原生配置选项;值必须符合该智能体的选项定义。
2800
+ * @param key - 原生配置选项 ID。
2801
+ * @param value - 该选项允许的值。
2802
+ * @returns 原生配置更新结果的 HaiResult。
2803
+ * @example
2804
+ * ```ts
2805
+ * // session 为 sessions.create/get/resume 成功返回的 data。
2806
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2807
+ * const result = await session.setConfigOption('model', 'coding')
2808
+ * ```
2809
+ */
2810
+ setConfigOption: (key, value) => protect(() => current().connection.setConfigOption(key, value)),
2811
+ /**
2812
+ * 先关闭会话执行环境,再 fetch 并仅快进各工程明确配置的主分支;不修改会话分支。
2813
+ * @returns 各仓更新报告;部分失败时报告位于 error.cause.reports。
2814
+ * @example
2815
+ * ```ts
2816
+ * // session 为 sessions.create/get/resume 成功返回的 data。
2817
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2818
+ * const result = await session.updateMain()
2819
+ * ```
2820
+ */
2821
+ updateMain: () => protect(() => gitOperation()),
2822
+ /**
2823
+ * 关闭执行环境后逐仓提交并推送;仅支持受管理的独立会话,部分失败保留各仓报告,禁止强推。
2824
+ * @param request - 发布模式、提交说明和作者;仅支持受管理的独立会话。
2825
+ * @returns 各仓提交/推送报告;部分失败时报告位于 error.cause.reports。
2826
+ * @example
2827
+ * ```ts
2828
+ * // session 为 sessions.create/get/resume 成功返回的 data。
2829
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2830
+ * const result = await session.publish({ mode: 'merge-main', message: '完成任务', author: { name: 'Developer', email: 'dev@example.com' } })
2831
+ * ```
2832
+ */
2833
+ publish: (request) => protect(() => gitOperation(request)),
2834
+ /**
2835
+ * 停止拥有的进程或容器,释放占用;保留工作目录和原生状态,供后续显式恢复。
2836
+ * @returns 关闭完成的 HaiResult;清理失败时不释放仍在占用的资源。
2837
+ * @example
2838
+ * ```ts
2839
+ * // session 为 sessions.create/get/resume 成功返回的 data。
2840
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2841
+ * const result = await session.close()
2842
+ * ```
2843
+ */
2844
+ close: () => protect(() => stop(id))
2845
+ };
2846
+ }
2847
+ return {
2848
+ agents: {
2849
+ /**
2850
+ * 校验模型与适配器后新增数据库智能体;配置同名条目只读。
2851
+ * @param input - 完整智能体定义,必须引用已有 Hai 模型 ID。
2852
+ * @returns 带 source/version 的新增条目;配置同 ID 返回只读错误。
2853
+ * @example
2854
+ * ```ts
2855
+ * import { ai } from '@h-ai/ai'
2856
+ * // 前提:ai 已成功初始化。
2857
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2858
+ * const result = await ai.acp.agents.register({ id: 'coder', name: 'Coder', mode: 'embedded', adapter: 'codex', modelIds: ['coding'] })
2859
+ * ```
2860
+ */
2861
+ register: (input) => protectResult(async () => {
2862
+ const agent = ACPAgentSchema.parse(input);
2863
+ await validateAgent(agent);
2864
+ return agentRegistry.operations.register(agent);
2865
+ }),
2866
+ /**
2867
+ * 按 ID 读取合并清单条目;source 区分配置与数据库,数据库 get 返回 version。
2868
+ * @param id - 智能体 ID。
2869
+ * @returns 合并后的资源条目;未找到时返回资源不存在错误。
2870
+ * @example
2871
+ * ```ts
2872
+ * import { ai } from '@h-ai/ai'
2873
+ * // 前提:ai 已成功初始化。
2874
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2875
+ * const result = await ai.acp.agents.get('coder')
2876
+ * ```
2877
+ */
2878
+ get: agentRegistry.operations.get,
2879
+ /**
2880
+ * 列出配置与数据库合并后的智能体;同 ID 配置优先。
2881
+ * @returns 去重后的智能体条目列表;列表项不带数据库 version。
2882
+ * @example
2883
+ * ```ts
2884
+ * import { ai } from '@h-ai/ai'
2885
+ * // 前提:ai 已成功初始化。
2886
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2887
+ * const result = await ai.acp.agents.list()
2888
+ * ```
2889
+ */
2890
+ list: agentRegistry.operations.list,
2891
+ /**
2892
+ * 整体替换数据库智能体;可传入 get 返回的版本防止覆盖并发修改,配置条目不可改。
2893
+ * @param input - 包含原 ID 的完整替换定义。
2894
+ * @param version - get 返回的数据库版本;传入时校验并发修改。
2895
+ * @returns 更新后的资源条目;配置只读、版本冲突均返回失败。
2896
+ * @example
2897
+ * ```ts
2898
+ * import { ai } from '@h-ai/ai'
2899
+ * // 前提:ai 已成功初始化。
2900
+ * // definition 为完整替换定义;entry 为 get 成功返回的条目。
2901
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2902
+ * const result = await ai.acp.agents.update(definition, entry.version)
2903
+ * ```
2904
+ */
2905
+ update: (input, version) => protectResult(async () => {
2906
+ const agent = ACPAgentSchema.parse(input);
2907
+ await validateAgent(agent);
2908
+ return agentRegistry.operations.update(agent, version);
2909
+ }),
2910
+ /**
2911
+ * 删除无会话引用的数据库智能体定义;不删除镜像或外部服务。
2912
+ * @param id - 数据库智能体 ID。
2913
+ * @param version - 可选的期望版本。
2914
+ * @returns 删除完成的 HaiResult;存在会话引用时返回 ACP_BUSY。
2915
+ * @example
2916
+ * ```ts
2917
+ * import { ai } from '@h-ai/ai'
2918
+ * // 前提:ai 已成功初始化。
2919
+ * // entry 为 get 成功返回的数据库条目。
2920
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2921
+ * const result = await ai.acp.agents.remove('coder', entry.version)
2922
+ * ```
2923
+ */
2924
+ remove: (id, version) => protectResult(async () => {
2925
+ return withDefinitions(async () => {
2926
+ const existing = await agentRegistry.read(id);
2927
+ if (existing?.source === "config")
2928
+ return agentRegistry.operations.remove(id, version);
2929
+ if ((await sessions.query({ where: { agentId: id } })).length)
2930
+ throw new ACPFailure("busy");
2931
+ return agentRegistry.operations.remove(id, version);
2932
+ });
2933
+ }),
2934
+ /**
2935
+ * 解析模型和原生配置,验证适配器是否兼容;不持久保存模型凭据。
2936
+ * @param id - 已登记智能体 ID。
2937
+ * @param modelId - 可选模型 ID,须在智能体 modelIds 中。
2938
+ * @returns 智能体模式与绑定模型摘要;不返回凭据或原生配置内容。
2939
+ * @example
2940
+ * ```ts
2941
+ * import { ai } from '@h-ai/ai'
2942
+ * // 前提:ai 已成功初始化。
2943
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2944
+ * const result = await ai.acp.agents.initialize('coder')
2945
+ * ```
2946
+ */
2947
+ initialize: (id, modelId) => protect(async () => {
2948
+ const agent = await getAgent(id);
2949
+ await validateAgent(agent);
2950
+ if (agent.mode === "custom-service")
2951
+ return { agentId: id, mode: agent.mode, modelIds: [] };
2952
+ await adapters[agent.adapter].configure({
2953
+ agent,
2954
+ models: resolveAgentModels(agent, await modelConfig()),
2955
+ modelId: modelId ?? agent.defaultModelId ?? agent.modelIds[0],
2956
+ statePath: "/state/validation"
2957
+ });
2958
+ return { agentId: id, mode: agent.mode, modelIds: agent.modelIds };
2959
+ }),
2960
+ /**
2961
+ * 实际启动并握手后关闭连接,返回智能体能力;托管模式需指定工作区。
2962
+ * @param id - 已登记智能体 ID。
2963
+ * @param options - 回调、能力及工作区;托管模式必须传 workspaceId。
2964
+ * @returns 原生初始化结果(智能体能力、认证方法等)的 HaiResult。
2965
+ * @example
2966
+ * ```ts
2967
+ * import { ai } from '@h-ai/ai'
2968
+ * // 前提:ai 已成功初始化。
2969
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
2970
+ * const result = await ai.acp.agents.check('coder', { workspaceId: 'project' })
2971
+ * ```
2972
+ */
2973
+ check: (id, options = {}) => protect(async () => {
2974
+ const agent = await getAgent(id);
2975
+ await validateAgent(agent);
2976
+ if (agent.mode !== "custom-service") {
2977
+ if (!options.workspaceId)
2978
+ throw new ACPFailure("invalid");
2979
+ const workspace = await getWorkspace(options.workspaceId);
2980
+ if (agent.mode === "embedded" && workspace.storage.type !== "directory" || agent.mode === "builtin-service" && (workspace.storage.type !== "volume" || workspace.storage.image !== agent.image)) {
2981
+ throw new ACPFailure("unsupported");
2982
+ }
2983
+ return workspaceOperation(workspace, async (runtime) => {
2984
+ const info = {
2985
+ id: randomUUID(),
2986
+ agentId: id,
2987
+ cwd: runtime.root,
2988
+ newWorktree: false,
2989
+ agent,
2990
+ status: "opening",
2991
+ mcpServers: [],
2992
+ modelId: agent.defaultModelId ?? agent.modelIds[0]
2993
+ };
2994
+ const connection2 = await connectACP(
2995
+ await launchAgent(agent, runtime, info),
2996
+ options,
2997
+ limits
2998
+ );
2999
+ try {
3000
+ return connection2.initialized;
3001
+ } finally {
3002
+ await connection2.close();
3003
+ }
3004
+ });
3005
+ }
3006
+ const connection = await connectACP(
3007
+ {
3008
+ type: agent.transport,
3009
+ url: agent.url,
3010
+ headers: resolveEnvironment(agent.headerEnv)
3011
+ },
3012
+ options,
3013
+ limits
3014
+ );
3015
+ try {
3016
+ return connection.initialized;
3017
+ } finally {
3018
+ await connection.close();
3019
+ }
3020
+ })
3021
+ },
3022
+ workspaces: {
3023
+ /**
3024
+ * 新增数据库工作区并归一化存储位置;拒绝目录重叠或同一 volume 重复登记。
3025
+ * @param input - 工作区存储位置、显式 Git 主分支和环境变量引用。
3026
+ * @returns 带来源和版本的工作区条目;重叠位置返回冲突错误。
3027
+ * @example
3028
+ * ```ts
3029
+ * import { ai } from '@h-ai/ai'
3030
+ * // 前提:ai 已成功初始化。
3031
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3032
+ * const result = await ai.acp.workspaces.register({ id: 'project', storage: { type: 'directory', path: '/srv/project' } })
3033
+ * ```
3034
+ */
3035
+ register: (input) => protectResult(async () => {
3036
+ const workspace = ACPWorkspaceSchema.parse(input);
3037
+ if ((await workspaceRegistry.read(workspace.id))?.source === "config")
3038
+ return workspaceRegistry.operations.register(workspace);
3039
+ await normalizeWorkspace(workspace);
3040
+ const release = await acquire("definition-registry");
3041
+ try {
3042
+ await checkWorkspaceOverlap(workspace);
3043
+ return await workspaceRegistry.operations.register(workspace);
3044
+ } finally {
3045
+ await release();
3046
+ }
3047
+ }),
3048
+ /**
3049
+ * 读取工作区定义及来源;配置条目无数据库版本。
3050
+ * @param id - 工作区 ID。
3051
+ * @returns 工作区资源条目;未找到时返回资源不存在错误。
3052
+ * @example
3053
+ * ```ts
3054
+ * import { ai } from '@h-ai/ai'
3055
+ * // 前提:ai 已成功初始化。
3056
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3057
+ * const result = await ai.acp.workspaces.get('project')
3058
+ * ```
3059
+ */
3060
+ get: workspaceRegistry.operations.get,
3061
+ /**
3062
+ * 列出配置与数据库合并后的工作区;不创建路径或克隆工程。
3063
+ * @returns 去重后的工作区列表;列表项不含数据库 version。
3064
+ * @example
3065
+ * ```ts
3066
+ * import { ai } from '@h-ai/ai'
3067
+ * // 前提:ai 已成功初始化。
3068
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3069
+ * const result = await ai.acp.workspaces.list()
3070
+ * ```
3071
+ */
3072
+ list: workspaceRegistry.operations.list,
3073
+ /**
3074
+ * 整体替换无会话引用的数据库工作区,重新检查存储位置重叠;配置条目不可修改。
3075
+ * @param input - 包含原 ID 的完整替换定义。
3076
+ * @param version - 可选的数据库期望版本。
3077
+ * @returns 更新后的工作区;有会话引用、位置冲突或版本冲突时失败。
3078
+ * @example
3079
+ * ```ts
3080
+ * import { ai } from '@h-ai/ai'
3081
+ * // 前提:ai 已成功初始化。
3082
+ * // definition 为完整替换定义;entry 为 get 成功返回的条目。
3083
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3084
+ * const result = await ai.acp.workspaces.update(definition, entry.version)
3085
+ * ```
3086
+ */
3087
+ update: (input, version) => protectResult(async () => {
3088
+ const workspace = ACPWorkspaceSchema.parse(input);
3089
+ const existing = await workspaceRegistry.read(workspace.id);
3090
+ if (existing?.source === "config")
3091
+ return workspaceRegistry.operations.update(workspace, version);
3092
+ const release = await acquire("definition-registry");
3093
+ try {
3094
+ if ((await sessions.query({ where: { workspaceId: workspace.id } })).length) {
3095
+ throw new ACPFailure("busy");
3096
+ }
3097
+ await normalizeWorkspace(workspace);
3098
+ await checkWorkspaceOverlap(workspace);
3099
+ return await workspaceRegistry.operations.update(
3100
+ workspace,
3101
+ version
3102
+ );
3103
+ } finally {
3104
+ await release();
3105
+ }
3106
+ }),
3107
+ /**
3108
+ * 创建目录并克隆缺失工程;已有内容不自动 pull 或覆盖,Git 不存在时返回明确错误。
3109
+ * @param id - 已登记工作区 ID。
3110
+ * @returns 准备后的 main 绝对目录;克隆失败保留现场供检查。
3111
+ * @example
3112
+ * ```ts
3113
+ * import { ai } from '@h-ai/ai'
3114
+ * // 前提:ai 已成功初始化。
3115
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3116
+ * const result = await ai.acp.workspaces.prepare('project')
3117
+ * ```
3118
+ */
3119
+ prepare: (id) => protect(
3120
+ async () => workspaceOperation(await getWorkspace(id), (runtime) => createGitOperationsInput(id, runtime))
3121
+ ),
3122
+ /**
3123
+ * 将有限大小的文件写入 main/files,拒绝覆盖、路径逃逸和符号链接;失败前已写入文件会保留。
3124
+ * @param id - 已登记工作区 ID。
3125
+ * @param files - 相对路径和 UTF-8 文本或二进制;不允许覆盖或路径逃逸。
3126
+ * @returns 已导入相对路径列表;写入中途失败不回滚已有文件。
3127
+ * @example
3128
+ * ```ts
3129
+ * import { ai } from '@h-ai/ai'
3130
+ * // 前提:ai 已成功初始化。
3131
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3132
+ * const result = await ai.acp.workspaces.importFiles('project', [{ path: 'notes.txt', content: '需求' }])
3133
+ * ```
3134
+ */
3135
+ importFiles: (id, files) => protect(async () => {
3136
+ if (files.length > 1e3 || files.reduce(
3137
+ (sum, file) => sum + Buffer.byteLength(file.content),
3138
+ 0
3139
+ ) > limits.maxContentBytes) {
3140
+ throw new ACPFailure("output");
3141
+ }
3142
+ for (const file of files) {
3143
+ if (!file.path || file.path.split(/[\\/]/).some(
3144
+ (part) => !part || part === "." || part === ".." || part === ".git"
3145
+ ) || isAbsolute(file.path)) {
3146
+ throw new ACPFailure("invalid");
3147
+ }
3148
+ }
3149
+ return workspaceOperation(await getWorkspace(id), async (runtime) => {
3150
+ await runtime.file("mkdir", "main/files");
3151
+ for (const file of files) {
3152
+ if (await runtime.file("exists", `main/files/${file.path}`) === "true") {
3153
+ throw new ACPFailure("conflict");
3154
+ }
3155
+ }
3156
+ for (const file of files) {
3157
+ await runtime.file(
3158
+ "write",
3159
+ `main/files/${file.path}`,
3160
+ Buffer.from(file.content).toString("base64")
3161
+ );
3162
+ }
3163
+ return { imported: files.map((file) => file.path) };
3164
+ });
3165
+ }),
3166
+ /**
3167
+ * 移除无会话引用的数据库定义,保留磁盘目录、Git 检出和 volume。
3168
+ * @param id - 数据库工作区 ID。
3169
+ * @param version - 可选的数据库期望版本。
3170
+ * @returns 删除定义的 HaiResult;引用存在时失败,文件始终保留。
3171
+ * @example
3172
+ * ```ts
3173
+ * import { ai } from '@h-ai/ai'
3174
+ * // 前提:ai 已成功初始化。
3175
+ * // entry 为 get 成功返回的数据库条目。
3176
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3177
+ * const result = await ai.acp.workspaces.remove('project', entry.version)
3178
+ * ```
3179
+ */
3180
+ remove: (id, version) => protectResult(async () => {
3181
+ return withDefinitions(async () => {
3182
+ const existing = await workspaceRegistry.read(id);
3183
+ if (existing?.source === "config")
3184
+ return workspaceRegistry.operations.remove(id, version);
3185
+ if ((await sessions.query({ where: { workspaceId: id } })).length)
3186
+ throw new ACPFailure("busy");
3187
+ return workspaceRegistry.operations.remove(id, version);
3188
+ });
3189
+ })
3190
+ },
3191
+ sessions: {
3192
+ /**
3193
+ * 绑定模型、工作区和 MCP,准备目录后建立原生会话;newWorktree 可选择独立工程分支。
3194
+ * @param options - 智能体、工作区、模型、MCP 及可信回调;newWorktree 默认 false。
3195
+ * @returns 可用于 prompt/stream/publish 的会话句柄;启动失败保留失败记录。
3196
+ * @example
3197
+ * ```ts
3198
+ * import { ai } from '@h-ai/ai'
3199
+ * // 前提:ai 已成功初始化。
3200
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3201
+ * const result = await ai.acp.sessions.create({ agentId: 'coder', workspaceId: 'project', newWorktree: true })
3202
+ * ```
3203
+ */
3204
+ create: (options) => protect(async () => {
3205
+ const info = await withDefinitions(async () => {
3206
+ const agent = await getAgent(options.agentId);
3207
+ await validateAgent(agent);
3208
+ if (!agent.enabled)
3209
+ throw new ACPFailure("unsupported");
3210
+ const id = sessionIdentitySchema.parse(options.id ?? randomUUID());
3211
+ const modelId = options.modelId ?? agent.defaultModelId ?? agent.modelIds[0];
3212
+ if (modelId && !agent.modelIds.includes(modelId))
3213
+ throw new ACPFailure("model");
3214
+ const info2 = {
3215
+ id,
3216
+ agentId: agent.id,
3217
+ workspaceId: options.workspaceId,
3218
+ cwd: options.cwd ?? "",
3219
+ newWorktree: options.newWorktree ?? false,
3220
+ branch: options.newWorktree ? branchNameSchema.parse(options.branch ?? `acp/${id}`) : void 0,
3221
+ modelId,
3222
+ modelSignature: await modelSignature(agent),
3223
+ status: "opening",
3224
+ agent,
3225
+ mcpServers: options.mcpServers ?? [],
3226
+ mcpServerIds: options.mcpServerIds
3227
+ };
3228
+ if (options.workspaceId)
3229
+ await getWorkspace(options.workspaceId);
3230
+ await unique(sessions, id, info2);
3231
+ return info2;
3232
+ });
3233
+ return start(info, options, false);
3234
+ }),
3235
+ /**
3236
+ * 获取持久会话的管理句柄;已关闭会话需要 resume 后才能执行 prompt。
3237
+ * @param id - 框架会话 ID,不是智能体原生 ID。
3238
+ * @returns 管理句柄的 HaiResult;不会自动重新启动智能体。
3239
+ * @example
3240
+ * ```ts
3241
+ * import { ai } from '@h-ai/ai'
3242
+ * // 前提:ai 已成功初始化。
3243
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3244
+ * const result = await ai.acp.sessions.get('task-1')
3245
+ * ```
3246
+ */
3247
+ get: (id) => protect(async () => {
3248
+ await get(sessions, id);
3249
+ return handle(id);
3250
+ }),
3251
+ /**
3252
+ * 列出持久会话,可按工作区 ID 过滤。
3253
+ * @param workspaceId - 可选工作区筛选条件;省略返回全部会话。
3254
+ * @returns 持久 ACPSessionInfo 列表的 HaiResult。
3255
+ * @example
3256
+ * ```ts
3257
+ * import { ai } from '@h-ai/ai'
3258
+ * // 前提:ai 已成功初始化。
3259
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3260
+ * const result = await ai.acp.sessions.list('project')
3261
+ * ```
3262
+ */
3263
+ list: (workspaceId) => protect(
3264
+ () => sessions.query({ where: workspaceId ? { workspaceId } : {} })
3265
+ ),
3266
+ /**
3267
+ * 使用原生会话 ID 和保留状态恢复;智能体定义或模型能力已改变时拒绝恢复。
3268
+ * @param id - 已关闭或失败的框架会话 ID。
3269
+ * @param options - 本次连接的回调、能力和取消信号,不从数据库恢复回调。
3270
+ * @returns 恢复后的会话句柄;原生服务不支持恢复或配置变化时失败。
3271
+ * @example
3272
+ * ```ts
3273
+ * import { ai } from '@h-ai/ai'
3274
+ * // 前提:ai 已成功初始化。
3275
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3276
+ * const result = await ai.acp.sessions.resume('task-1')
3277
+ * ```
3278
+ */
3279
+ resume: (id, options = {}) => protect(async () => {
3280
+ const info = await get(sessions, id);
3281
+ if (info.status === "opening" || info.status === "active")
3282
+ throw new ACPFailure("busy");
3283
+ const agent = await getAgent(info.agentId);
3284
+ if (!agent.enabled || JSON.stringify(agent) !== JSON.stringify(info.agent)) {
3285
+ throw new ACPFailure("conflict");
3286
+ }
3287
+ if (info.modelSignature !== await modelSignature(agent))
3288
+ throw new ACPFailure("conflict");
3289
+ return start(info, options, true);
3290
+ }),
3291
+ /**
3292
+ * 关闭本进程拥有的会话并释放锁,不删除历史和工程内容。
3293
+ * @param id - 框架会话 ID。
3294
+ * @returns 关闭完成的 HaiResult;不允许关闭其他进程拥有的执行环境。
3295
+ * @example
3296
+ * ```ts
3297
+ * import { ai } from '@h-ai/ai'
3298
+ * // 前提:ai 已成功初始化。
3299
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3300
+ * const result = await ai.acp.sessions.close('task-1')
3301
+ * ```
3302
+ */
3303
+ close: (id) => protect(() => stop(id)),
3304
+ /**
3305
+ * 仅删除已关闭或失败的会话记录;不删除工作树及原生文件,不替代 Git worktree 清理。
3306
+ * @param id - 框架会话 ID。
3307
+ * @returns 删除完成的 HaiResult;活动会话返回忙碌错误。
3308
+ * @example
3309
+ * ```ts
3310
+ * import { ai } from '@h-ai/ai'
3311
+ * // 前提:ai 已成功初始化。
3312
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3313
+ * const result = await ai.acp.sessions.remove('task-1')
3314
+ * ```
3315
+ */
3316
+ remove: (id) => protect(async () => {
3317
+ const info = await get(sessions, id);
3318
+ if (live.has(id) || info.status === "active" || info.status === "opening") {
3319
+ throw new ACPFailure("busy");
3320
+ }
3321
+ const release = await acquire(`session:${id}`);
3322
+ try {
3323
+ await sessions.remove(id);
3324
+ } finally {
3325
+ await release();
3326
+ }
3327
+ })
3328
+ },
3329
+ /**
3330
+ * 连接原生 stdio/HTTP/WS 服务;不创建工作区或提供传输兼容桥。
3331
+ * @param transport - 智能体实际支持的原生传输与命令或地址。
3332
+ * @param options - 可信回调、声明能力和初始化取消信号。
3333
+ * @returns 低层连接句柄的 HaiResult;连接失败不自动转换传输。
3334
+ * @example
3335
+ * ```ts
3336
+ * import { ai } from '@h-ai/ai'
3337
+ * // 前提:ai 已成功初始化。
3338
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3339
+ * const result = await ai.acp.connect({ type: 'http', url: 'https://agent.example.com/acp' })
3340
+ * ```
3341
+ */
3342
+ connect: (transport, options = {}) => protect(async () => {
3343
+ const connection = await connectACP(transport, options, limits);
3344
+ connections.add(connection);
3345
+ return {
3346
+ info: connection.initialized,
3347
+ /**
3348
+ * 选择对端握手公布的认证方法,返回 HaiResult。
3349
+ * @param method - initialize 返回的认证方法 ID。
3350
+ * @returns 认证响应的 HaiResult。
3351
+ * @example
3352
+ * ```ts
3353
+ * // connection/session 为已成功连接或创建的句柄,先检查 result.success。
3354
+ * const result = await connection.authenticate('api-key')
3355
+ * ```
3356
+ */
3357
+ authenticate: (method) => protect(() => connection.authenticate(method)),
3358
+ /**
3359
+ * 在对端可见的绝对 cwd 创建原生会话,返回 prompt/cancel 句柄。
3360
+ * @param cwd - 对端文件系统可见的绝对工作目录。
3361
+ * @param mcpServers - 可选原生 MCP 定义;调用方负责凭据与授权。
3362
+ * @returns 原生会话句柄;不创建框架持久会话记录。
3363
+ * @example
3364
+ * ```ts
3365
+ * // connection/session 为已成功连接或创建的句柄,先检查 result.success。
3366
+ * const result = await connection.createSession('/srv/project', [])
3367
+ * ```
3368
+ */
3369
+ createSession: (cwd, mcpServers) => protect(async () => {
3370
+ const session = await connection.createSession(cwd, mcpServers);
3371
+ return {
3372
+ id: session.sessionId,
3373
+ /**
3374
+ * 发送一轮原生内容,返回最终文本、停止原因和事件。
3375
+ * @param content - 原生 ACP 内容块。
3376
+ * @param signal - 可选本轮取消信号。
3377
+ * @returns 最终文本、停止原因和事件的 HaiResult。
3378
+ * @example
3379
+ * ```ts
3380
+ * // connection/session 为已成功连接或创建的句柄,先检查 result.success。
3381
+ * const result = await session.prompt([{ type: 'text', text: '检查工程' }])
3382
+ * ```
3383
+ */
3384
+ prompt: (content, signal) => protect(() => connection.prompt(content, signal)),
3385
+ /**
3386
+ * 取消当前轮次,超时则关闭连接。
3387
+ * @returns 取消完成的 HaiResult。
3388
+ * @example
3389
+ * ```ts
3390
+ * // connection/session 为已成功连接或创建的句柄,先检查 result.success。
3391
+ * const result = await session.cancel()
3392
+ * ```
3393
+ */
3394
+ cancel: () => protect(() => connection.cancel())
3395
+ };
3396
+ }),
3397
+ /**
3398
+ * 读取对端原生会话列表;未声明相应能力时返回错误。
3399
+ * @param cursor - 上一次原生列表响应中的分页游标。
3400
+ * @returns 原生分页响应的 HaiResult。
3401
+ * @example
3402
+ * ```ts
3403
+ * // connection/session 为已成功连接或创建的句柄,先检查 result.success。
3404
+ * const result = await connection.listSessions()
3405
+ * ```
3406
+ */
3407
+ listSessions: (cursor) => protect(() => connection.listSessions(cursor)),
3408
+ /**
3409
+ * 按原生 ID 删除远端会话;不会删除框架数据库会话记录。
3410
+ * @param id - 对端返回的原生会话 ID。
3411
+ * @returns 原生删除响应的 HaiResult;不支持时返回协议错误。
3412
+ * @example
3413
+ * ```ts
3414
+ * // connection/session 为已成功连接或创建的句柄,先检查 result.success。
3415
+ * const result = await connection.deleteSession('native-id')
3416
+ * ```
3417
+ */
3418
+ deleteSession: (id) => protect(() => connection.deleteSession(id)),
3419
+ /**
3420
+ * 关闭连接和拥有的子进程;不删除远端文件或持久状态。
3421
+ * @returns 关闭完成的 HaiResult;可重复关闭。
3422
+ * @example
3423
+ * ```ts
3424
+ * // connection/session 为已成功连接或创建的句柄,先检查 result.success。
3425
+ * const result = await connection.close()
3426
+ * ```
3427
+ */
3428
+ close: () => protect(async () => {
3429
+ await connection.close();
3430
+ connections.delete(connection);
3431
+ })
3432
+ };
3433
+ }),
3434
+ /**
3435
+ * 关闭子系统及拥有的资源;由 ai.close 生命周期调用。
3436
+ * @returns 所有已接受操作结束和拥有资源完成清理后的 Promise。
3437
+ * @throws 清理进程或容器失败时抛出 ACPFailure,由 ai.close 统一处理。
3438
+ * @example
3439
+ * ```ts
3440
+ * import { ai } from '@h-ai/ai'
3441
+ * // 前提:ai 已成功初始化。
3442
+ * // 普通 API 先检查 result.success,再读取 data 或 error。
3443
+ * await ai.close()
3444
+ * ```
3445
+ */
3446
+ async close() {
3447
+ closed = true;
3448
+ await Promise.allSettled(
3449
+ [...live.values()].map((value) => value.connection.cancel())
3450
+ );
3451
+ await Promise.allSettled(
3452
+ [...connections].map((connection) => connection.close())
3453
+ );
3454
+ await Promise.allSettled([...pending]);
3455
+ const results = await Promise.allSettled([...live.keys()].map(stop));
3456
+ const more = await Promise.allSettled(
3457
+ [...connections].map((connection) => connection.close())
3458
+ );
3459
+ connections.clear();
3460
+ if ([...results, ...more].some((result) => result.status === "rejected"))
3461
+ throw new ACPFailure("cleanup");
3462
+ }
3463
+ };
3464
+ async function createGitOperationsInput(id, runtime) {
3465
+ return createGitOperations(await getWorkspace(id), runtime).prepare();
3466
+ }
3467
+ }
705
3468
  var SENTENCE_BOUNDARY_REGEX = /[。!?;!?;\n]|\.(?=\s|$)/;
706
3469
  async function* streamSentences(stream) {
707
3470
  let buffer = "";
@@ -799,8 +3562,8 @@ async function describeHttpError(response) {
799
3562
  }
800
3563
  var logger3 = core.logger.child({ module: "ai", scope: "audio-ws" });
801
3564
  function openAudioWebSocket(url, headers, options) {
802
- return new Promise((resolve, reject) => {
803
- const ws = new WebSocket(url, { headers, handshakeTimeout: options?.timeout ?? 15e3 });
3565
+ return new Promise((resolve3, reject) => {
3566
+ const ws = new WebSocket2(url, { headers, handshakeTimeout: options?.timeout ?? 15e3 });
804
3567
  ws.binaryType = "arraybuffer";
805
3568
  const queue = [];
806
3569
  let waiting = null;
@@ -903,7 +3666,7 @@ function openAudioWebSocket(url, headers, options) {
903
3666
  }
904
3667
  }
905
3668
  };
906
- resolve(connection);
3669
+ resolve3(connection);
907
3670
  });
908
3671
  });
909
3672
  }
@@ -914,7 +3677,7 @@ var DOUBAO_CAPABILITIES = {
914
3677
  transcribe: { supported: true, realtimeAudioInput: true, speechBoundaryEvents: false, streamingTranscriptOutput: true },
915
3678
  synthesize: { supported: true, incrementalTextInput: true, streamingAudioOutput: true }
916
3679
  };
917
- var PROTOCOL_VERSION = 1;
3680
+ var PROTOCOL_VERSION2 = 1;
918
3681
  var HEADER_SIZE = 1;
919
3682
  var MSG_FULL_CLIENT = 1;
920
3683
  var MSG_AUDIO_ONLY_CLIENT = 2;
@@ -952,7 +3715,7 @@ var DOUBAO_FORMAT = {
952
3715
  };
953
3716
  function buildHeader(messageType, flags, serialization, compression) {
954
3717
  return Buffer.from([
955
- PROTOCOL_VERSION << 4 | HEADER_SIZE,
3718
+ PROTOCOL_VERSION2 << 4 | HEADER_SIZE,
956
3719
  messageType << 4 | flags,
957
3720
  serialization << 4 | compression,
958
3721
  0
@@ -1726,9 +4489,9 @@ function createOpenAIAudioProvider() {
1726
4489
  if (!model.apiKey)
1727
4490
  return err(HaiAIError.CONFIGURATION_ERROR, aiM("ai_audioMissingApiKey", { params: { provider: "openai" } }));
1728
4491
  try {
1729
- const client = createClient(model.apiKey, model.baseUrl, model.timeout);
4492
+ const client2 = createClient(model.apiKey, model.baseUrl, model.timeout);
1730
4493
  const file = await toUploadFile(audio);
1731
- const result = await client.audio.transcriptions.create({
4494
+ const result = await client2.audio.transcriptions.create({
1732
4495
  file,
1733
4496
  model: model.model,
1734
4497
  language,
@@ -1750,9 +4513,9 @@ function createOpenAIAudioProvider() {
1750
4513
  aiM("ai_audioUnsupportedInput", { params: { provider: "openai", reason: "streaming audio input" } })
1751
4514
  );
1752
4515
  }
1753
- const client = createClient(model.apiKey, model.baseUrl, model.timeout);
4516
+ const client2 = createClient(model.apiKey, model.baseUrl, model.timeout);
1754
4517
  const file = await toUploadFile(audio);
1755
- const stream = await client.audio.transcriptions.create({
4518
+ const stream = await client2.audio.transcriptions.create({
1756
4519
  file,
1757
4520
  model: model.model,
1758
4521
  language,
@@ -1775,8 +4538,8 @@ function createOpenAIAudioProvider() {
1775
4538
  return err(HaiAIError.CONFIGURATION_ERROR, aiM("ai_audioMissingApiKey", { params: { provider: "openai" } }));
1776
4539
  const out = resolveSynthesisOutput4({ format, sampleRate });
1777
4540
  try {
1778
- const client = createClient(model.apiKey, model.baseUrl, model.timeout);
1779
- const response = await client.audio.speech.create({
4541
+ const client2 = createClient(model.apiKey, model.baseUrl, model.timeout);
4542
+ const response = await client2.audio.speech.create({
1780
4543
  model: model.model,
1781
4544
  voice: voice ?? "alloy",
1782
4545
  input: text,
@@ -1795,12 +4558,12 @@ function createOpenAIAudioProvider() {
1795
4558
  if (!model.apiKey)
1796
4559
  throw audioError(HaiAIError.CONFIGURATION_ERROR, aiM("ai_audioMissingApiKey", { params: { provider: "openai" } }));
1797
4560
  const outFormat = resolveSynthesisOutput4({ format }).format;
1798
- const client = createClient(model.apiKey, model.baseUrl, model.timeout);
4561
+ const client2 = createClient(model.apiKey, model.baseUrl, model.timeout);
1799
4562
  const segments = typeof text === "string" ? [text] : streamSentences(text);
1800
4563
  for await (const segment of segments) {
1801
4564
  if (!segment)
1802
4565
  continue;
1803
- const response = await client.audio.speech.create({
4566
+ const response = await client2.audio.speech.create({
1804
4567
  model: model.model,
1805
4568
  voice: voice ?? "alloy",
1806
4569
  input: segment,
@@ -2854,12 +5617,12 @@ function createContextOperations(compressConfig, tokenOps, compressOps, contextS
2854
5617
  delete candidate.active;
2855
5618
  }
2856
5619
  async function waitForContext(signal) {
2857
- await new Promise((resolve, reject) => {
5620
+ await new Promise((resolve3, reject) => {
2858
5621
  const timer = setTimeout(finish, 50);
2859
5622
  function finish() {
2860
5623
  clearTimeout(timer);
2861
5624
  signal?.removeEventListener("abort", abort);
2862
- resolve();
5625
+ resolve3();
2863
5626
  }
2864
5627
  function abort() {
2865
5628
  clearTimeout(timer);
@@ -3579,7 +6342,7 @@ function createEmbeddingOperations(config) {
3579
6342
  const resolved = resolvedResult.data;
3580
6343
  const input = Array.isArray(request.input) ? request.input : [request.input];
3581
6344
  try {
3582
- const client = getOrCreateClient(resolved);
6345
+ const client2 = getOrCreateClient(resolved);
3583
6346
  const params = {
3584
6347
  model: resolved.model,
3585
6348
  input
@@ -3587,7 +6350,7 @@ function createEmbeddingOperations(config) {
3587
6350
  if (request.dimensions ?? embeddingConfig.dimensions) {
3588
6351
  params.dimensions = request.dimensions ?? embeddingConfig.dimensions;
3589
6352
  }
3590
- const response = await client.embeddings.create(params);
6353
+ const response = await client2.embeddings.create(params);
3591
6354
  return ok({
3592
6355
  model: response.model,
3593
6356
  data: response.data.map((item) => ({
@@ -5217,9 +7980,9 @@ ${contextText}
5217
7980
  async ingestFile(input) {
5218
7981
  try {
5219
7982
  const { readFile } = await import('fs/promises');
5220
- const { basename } = await import('path');
7983
+ const { basename: basename2 } = await import('path');
5221
7984
  const content = await readFile(input.filePath, { encoding: input.encoding ?? "utf-8" });
5222
- const fileName = basename(input.filePath);
7985
+ const fileName = basename2(input.filePath);
5223
7986
  const documentId = input.documentId ?? fileName;
5224
7987
  return this.ingest({
5225
7988
  documentId,
@@ -5294,13 +8057,13 @@ async function runWithCancellation(run, context) {
5294
8057
  const { signal } = context;
5295
8058
  if (signal.aborted)
5296
8059
  throw new DOMException("Aborted", "AbortError");
5297
- return await new Promise((resolve, reject) => {
8060
+ return await new Promise((resolve3, reject) => {
5298
8061
  const onAbort = () => reject(new DOMException("Aborted", "AbortError"));
5299
8062
  signal.addEventListener("abort", onAbort, { once: true });
5300
8063
  Promise.resolve().then(() => run(context)).then(
5301
8064
  (value) => {
5302
8065
  signal.removeEventListener("abort", onAbort);
5303
- resolve(value);
8066
+ resolve3(value);
5304
8067
  },
5305
8068
  (error) => {
5306
8069
  signal.removeEventListener("abort", onAbort);
@@ -5612,11 +8375,11 @@ function createAnthropicProvider(deps) {
5612
8375
  function resolveAnthropic(requestModel, tempModel) {
5613
8376
  const entry = requestModel ? config.llm.models?.find((m) => m.id === requestModel || m.model === requestModel) : void 0;
5614
8377
  const model = tempModel?.model ?? entry?.model ?? requestModel ?? config.llm.model ?? "claude-3-5-sonnet-latest";
5615
- const apiKey = tempModel?.apiKey ?? entry?.apiKey ?? config.llm.apiKey ?? (process.env.HAI_AI_LLM_APIKEY?.trim() || process.env.ANTHROPIC_API_KEY);
8378
+ const apiKey = tempModel?.apiKey ?? entry?.apiKey ?? config.llm.apiKey ?? (process2.env.HAI_AI_LLM_APIKEY?.trim() || process2.env.ANTHROPIC_API_KEY);
5616
8379
  if (!apiKey)
5617
8380
  return err(HaiAIError.CONFIGURATION_ERROR, aiM("ai_configError", { params: { error: "API Key is required for Anthropic" } }));
5618
- const globalBaseUrl = config.llm.protocol === "anthropic" ? config.llm.baseUrl ?? (process.env.HAI_AI_LLM_BASEURL?.trim() || void 0) : void 0;
5619
- const baseUrl = (tempModel?.baseUrl ?? entry?.baseUrl ?? globalBaseUrl ?? process.env.ANTHROPIC_BASE_URL ?? DEFAULT_ANTHROPIC_BASE_URL).replace(/\/+$/, "");
8381
+ const globalBaseUrl = config.llm.protocol === "anthropic" ? config.llm.baseUrl ?? (process2.env.HAI_AI_LLM_BASEURL?.trim() || void 0) : void 0;
8382
+ const baseUrl = (tempModel?.baseUrl ?? entry?.baseUrl ?? globalBaseUrl ?? process2.env.ANTHROPIC_BASE_URL ?? DEFAULT_ANTHROPIC_BASE_URL).replace(/\/+$/, "");
5620
8383
  return ok({
5621
8384
  apiKey,
5622
8385
  baseUrl,
@@ -5926,12 +8689,12 @@ function createOpenAIProvider(deps) {
5926
8689
  const tempCacheTtl = config.llm.tempModelCacheTtl ?? 6e5;
5927
8690
  function getClient(apiKey, baseURL, timeout) {
5928
8691
  const key = `${apiKey}::${baseURL}::${timeout}`;
5929
- let client = clientCache.get(key);
5930
- if (!client) {
5931
- client = new OpenAI3({ apiKey, baseURL, timeout });
5932
- clientCache.set(key, client);
8692
+ let client2 = clientCache.get(key);
8693
+ if (!client2) {
8694
+ client2 = new OpenAI3({ apiKey, baseURL, timeout });
8695
+ clientCache.set(key, client2);
5933
8696
  }
5934
- return client;
8697
+ return client2;
5935
8698
  }
5936
8699
  function getTempClient(apiKey, baseURL, timeout) {
5937
8700
  const now = Date.now();
@@ -5943,9 +8706,9 @@ function createOpenAIProvider(deps) {
5943
8706
  const cached = tempClientCache.get(key);
5944
8707
  if (cached)
5945
8708
  return cached.client;
5946
- const client = new OpenAI3({ apiKey, baseURL, timeout });
5947
- tempClientCache.set(key, { client, expiresAt: now + tempCacheTtl });
5948
- return client;
8709
+ const client2 = new OpenAI3({ apiKey, baseURL, timeout });
8710
+ tempClientCache.set(key, { client: client2, expiresAt: now + tempCacheTtl });
8711
+ return client2;
5949
8712
  }
5950
8713
  function resolveClient(requestModel, tempModel) {
5951
8714
  if (tempModel)
@@ -5960,10 +8723,10 @@ function createOpenAIProvider(deps) {
5960
8723
  return ok({ client: getClient(apiKey ?? "", baseUrl ?? "https://api.openai.com/v1", timeout), model, protocol: resolvedApi });
5961
8724
  }
5962
8725
  function resolveTempClient(tempModel) {
5963
- const apiKey = tempModel.apiKey ?? config.llm.apiKey ?? (process.env.HAI_AI_LLM_APIKEY?.trim() || process.env.OPENAI_API_KEY);
8726
+ const apiKey = tempModel.apiKey ?? config.llm.apiKey ?? (process2.env.HAI_AI_LLM_APIKEY?.trim() || process2.env.OPENAI_API_KEY);
5964
8727
  if (!apiKey)
5965
8728
  return err(HaiAIError.CONFIGURATION_ERROR, aiM("ai_configError", { params: { error: "API Key is required for temp model" } }));
5966
- const baseUrl = tempModel.baseUrl ?? config.llm.baseUrl ?? (process.env.HAI_AI_LLM_BASEURL?.trim() || process.env.OPENAI_BASE_URL) ?? "https://api.openai.com/v1";
8729
+ const baseUrl = tempModel.baseUrl ?? config.llm.baseUrl ?? (process2.env.HAI_AI_LLM_BASEURL?.trim() || process2.env.OPENAI_BASE_URL) ?? "https://api.openai.com/v1";
5967
8730
  const timeout = tempModel.timeout ?? config.llm.timeout ?? 6e4;
5968
8731
  const api = (tempModel.protocol ?? config.llm.protocol) === "responses" ? "responses" : "chat";
5969
8732
  return ok({
@@ -5995,11 +8758,11 @@ function createOpenAIProvider(deps) {
5995
8758
  if (!clientResult.success)
5996
8759
  return clientResult;
5997
8760
  const resolved = clientResult.data;
5998
- const { client, model, maxTokens, temperature, protocol: api } = resolved;
8761
+ const { client: client2, model, maxTokens, temperature, protocol: api } = resolved;
5999
8762
  const { objectId: _objectId, sessionId: _sessionId, tempModel: _tempModel, signal, ...openaiRequest } = request;
6000
8763
  try {
6001
8764
  if (api === "responses") {
6002
- const response2 = await client.responses.create(buildResponsesParams(request, resolved), { signal });
8765
+ const response2 = await client2.responses.create(buildResponsesParams(request, resolved), { signal });
6003
8766
  return ok(responsesToChatResponse(response2, model));
6004
8767
  }
6005
8768
  const openaiMessages = request.messages.map(toOpenAIMessage);
@@ -6011,7 +8774,7 @@ function createOpenAIProvider(deps) {
6011
8774
  ...temperature !== void 0 && openaiRequest.temperature == null ? { temperature } : {},
6012
8775
  stream: false
6013
8776
  };
6014
- const response = signal ? await client.chat.completions.create(params, { signal }) : await client.chat.completions.create(params);
8777
+ const response = signal ? await client2.chat.completions.create(params, { signal }) : await client2.chat.completions.create(params);
6015
8778
  return ok(response);
6016
8779
  } catch (error) {
6017
8780
  const mapped = toAIError(error);
@@ -6023,10 +8786,10 @@ function createOpenAIProvider(deps) {
6023
8786
  if (!clientResult.success)
6024
8787
  throw new Error(clientResult.error.message);
6025
8788
  const resolved = clientResult.data;
6026
- const { client, model, maxTokens, temperature, protocol: api } = resolved;
8789
+ const { client: client2, model, maxTokens, temperature, protocol: api } = resolved;
6027
8790
  const { objectId: _objectId, sessionId: _sessionId, tempModel: _tempModel, signal, ...openaiRequest } = request;
6028
8791
  if (api === "responses") {
6029
- const responsesStream = await client.responses.create(
8792
+ const responsesStream = await client2.responses.create(
6030
8793
  { ...buildResponsesParams(request, resolved), stream: true },
6031
8794
  { signal }
6032
8795
  );
@@ -6046,7 +8809,7 @@ function createOpenAIProvider(deps) {
6046
8809
  ...temperature !== void 0 && openaiRequest.temperature == null ? { temperature } : {},
6047
8810
  stream: true
6048
8811
  };
6049
- const stream = signal ? await client.chat.completions.create(params, { signal }) : await client.chat.completions.create(params);
8812
+ const stream = signal ? await client2.chat.completions.create(params, { signal }) : await client2.chat.completions.create(params);
6050
8813
  for await (const chunk of stream) {
6051
8814
  yield chunk;
6052
8815
  }
@@ -6055,9 +8818,9 @@ function createOpenAIProvider(deps) {
6055
8818
  const clientResult = resolveClient();
6056
8819
  if (!clientResult.success)
6057
8820
  return clientResult;
6058
- const { client } = clientResult.data;
8821
+ const { client: client2 } = clientResult.data;
6059
8822
  try {
6060
- const response = await client.models.list();
8823
+ const response = await client2.models.list();
6061
8824
  const models = response.data.map((m) => m.id);
6062
8825
  return ok(models);
6063
8826
  } catch (error) {
@@ -6093,7 +8856,19 @@ function createLLMProvider(deps) {
6093
8856
  // src/llm/ai-llm-functions.ts
6094
8857
  var logger18 = core.logger.child({ module: "ai", scope: "llm" });
6095
8858
  function createAILLMFunctions(config, deps) {
6096
- const provider = createLLMProvider({ config });
8859
+ let provider = createLLMProvider({ config });
8860
+ let signature = JSON.stringify(config.llm);
8861
+ async function currentProvider() {
8862
+ if (!deps?.resolveConfig)
8863
+ return provider;
8864
+ const snapshot2 = await deps.resolveConfig();
8865
+ const next = JSON.stringify(snapshot2.llm);
8866
+ if (next !== signature) {
8867
+ provider = createLLMProvider({ config: snapshot2 });
8868
+ signature = next;
8869
+ }
8870
+ return provider;
8871
+ }
6097
8872
  const recordStore = deps?.recordStore;
6098
8873
  const sessionStore = deps?.sessionStore;
6099
8874
  let recordSeq = 0;
@@ -6108,13 +8883,26 @@ function createAILLMFunctions(config, deps) {
6108
8883
  return "New Session";
6109
8884
  }
6110
8885
  async function saveRecordAndSession(record, messages) {
6111
- await recordStore.save(record.id, record, { objectId: record.objectId, sessionId: record.sessionId });
6112
- logger18.trace("Chat record saved", { id: record.id, objectId: record.objectId, sessionId: record.sessionId });
8886
+ await recordStore.save(record.id, record, {
8887
+ objectId: record.objectId,
8888
+ sessionId: record.sessionId
8889
+ });
8890
+ logger18.trace("Chat record saved", {
8891
+ id: record.id,
8892
+ objectId: record.objectId,
8893
+ sessionId: record.sessionId
8894
+ });
6113
8895
  if (sessionStore) {
6114
8896
  await updateSessionInfo(sessionStore, record, (existing, now) => {
6115
8897
  if (existing?.deleted)
6116
8898
  return void 0;
6117
- return existing ? { ...existing, updatedAt: now } : { sessionId: record.sessionId, objectId: record.objectId, title: extractTitle(messages), createdAt: now, updatedAt: now };
8899
+ return existing ? { ...existing, updatedAt: now } : {
8900
+ sessionId: record.sessionId,
8901
+ objectId: record.objectId,
8902
+ title: extractTitle(messages),
8903
+ createdAt: now,
8904
+ updatedAt: now
8905
+ };
6118
8906
  });
6119
8907
  }
6120
8908
  }
@@ -6122,9 +8910,20 @@ function createAILLMFunctions(config, deps) {
6122
8910
  if (request.tempModel && Object.hasOwn(request.tempModel, "api"))
6123
8911
  return err(HaiAIError.INVALID_REQUEST, aiM("ai_llmProtocolRenamed"));
6124
8912
  const start = Date.now();
6125
- const context = { model: request.model ?? request.tempModel?.model, messageCount: request.messages.length };
8913
+ const context = {
8914
+ model: request.model ?? request.tempModel?.model,
8915
+ messageCount: request.messages.length
8916
+ };
6126
8917
  logger18.debug("LLM chat started", context);
6127
- const result = await provider.chat(request);
8918
+ let result;
8919
+ try {
8920
+ result = await (await currentProvider()).chat(request);
8921
+ } catch {
8922
+ return err(
8923
+ HaiAIError.RESOURCE_OPERATION_FAILED,
8924
+ aiM("ai_resourceOperationFailed")
8925
+ );
8926
+ }
6128
8927
  if (result.success) {
6129
8928
  logger18.info("LLM chat completed", {
6130
8929
  ...context,
@@ -6157,7 +8956,11 @@ function createAILLMFunctions(config, deps) {
6157
8956
  content: choice?.message?.content ?? "",
6158
8957
  toolCalls: choice?.message?.tool_calls,
6159
8958
  finishReason: choice?.finish_reason ?? "stop",
6160
- usage: response.usage ?? { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }
8959
+ usage: response.usage ?? {
8960
+ prompt_tokens: 0,
8961
+ completion_tokens: 0,
8962
+ total_tokens: 0
8963
+ }
6161
8964
  },
6162
8965
  createdAt: Date.now(),
6163
8966
  duration: Date.now() - start
@@ -6173,7 +8976,10 @@ function createAILLMFunctions(config, deps) {
6173
8976
  if (request.tempModel && Object.hasOwn(request.tempModel, "api"))
6174
8977
  throw new Error(aiM("ai_llmProtocolRenamed"));
6175
8978
  const start = Date.now();
6176
- const logContext = { model: request.model ?? request.tempModel?.model, messageCount: request.messages.length };
8979
+ const logContext = {
8980
+ model: request.model ?? request.tempModel?.model,
8981
+ messageCount: request.messages.length
8982
+ };
6177
8983
  logger18.debug("LLM chat stream started", logContext);
6178
8984
  const shouldRecord = !!(request.objectId && recordStore && request.enablePersist !== false);
6179
8985
  let content = "";
@@ -6182,7 +8988,7 @@ function createAILLMFunctions(config, deps) {
6182
8988
  let streamId = "";
6183
8989
  let resolvedModel = request.model ?? request.tempModel?.model ?? "";
6184
8990
  try {
6185
- for await (const chunk of provider.chatStream(request)) {
8991
+ for await (const chunk of (await currentProvider()).chatStream(request)) {
6186
8992
  yield chunk;
6187
8993
  if (!shouldRecord)
6188
8994
  continue;
@@ -6198,7 +9004,11 @@ function createAILLMFunctions(config, deps) {
6198
9004
  if (choice.delta.tool_calls) {
6199
9005
  for (const tc of choice.delta.tool_calls) {
6200
9006
  if (!toolCalls[tc.index]) {
6201
- toolCalls[tc.index] = { id: tc.id ?? "", type: "function", function: { name: tc.function?.name ?? "", arguments: "" } };
9007
+ toolCalls[tc.index] = {
9008
+ id: tc.id ?? "",
9009
+ type: "function",
9010
+ function: { name: tc.function?.name ?? "", arguments: "" }
9011
+ };
6202
9012
  }
6203
9013
  if (tc.function?.arguments)
6204
9014
  toolCalls[tc.index].function.arguments += tc.function.arguments;
@@ -6218,12 +9028,20 @@ function createAILLMFunctions(config, deps) {
6218
9028
  finishReason
6219
9029
  });
6220
9030
  } catch (error) {
6221
- logger18.warn("LLM chat stream failed", { ...logContext, durationMs: Date.now() - start, error });
9031
+ logger18.warn("LLM chat stream failed", {
9032
+ ...logContext,
9033
+ durationMs: Date.now() - start,
9034
+ error
9035
+ });
6222
9036
  throw error;
6223
9037
  }
6224
9038
  if (shouldRecord) {
6225
9039
  try {
6226
- const usage = { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 };
9040
+ const usage = {
9041
+ prompt_tokens: 0,
9042
+ completion_tokens: 0,
9043
+ total_tokens: 0
9044
+ };
6227
9045
  const record = {
6228
9046
  id: `${streamId || "stream"}_${Date.now()}_${recordSeq++}`,
6229
9047
  objectId: request.objectId,
@@ -6260,7 +9078,15 @@ function createAILLMFunctions(config, deps) {
6260
9078
  });
6261
9079
  return ok(records);
6262
9080
  } catch (error) {
6263
- return err(HaiAIError.LLM_HISTORY_FAILED, aiM("ai_llmHistoryFailed", { params: { error: error instanceof Error ? error.message : String(error) } }), error);
9081
+ return err(
9082
+ HaiAIError.LLM_HISTORY_FAILED,
9083
+ aiM("ai_llmHistoryFailed", {
9084
+ params: {
9085
+ error: error instanceof Error ? error.message : String(error)
9086
+ }
9087
+ }),
9088
+ error
9089
+ );
6264
9090
  }
6265
9091
  }
6266
9092
  async function listSessions(objectId) {
@@ -6274,13 +9100,30 @@ function createAILLMFunctions(config, deps) {
6274
9100
  });
6275
9101
  return ok(sessions.filter((item) => !item.deleted));
6276
9102
  } catch (error) {
6277
- return err(HaiAIError.LLM_HISTORY_FAILED, aiM("ai_llmHistoryFailed", { params: { error: error instanceof Error ? error.message : String(error) } }), error);
9103
+ return err(
9104
+ HaiAIError.LLM_HISTORY_FAILED,
9105
+ aiM("ai_llmHistoryFailed", {
9106
+ params: {
9107
+ error: error instanceof Error ? error.message : String(error)
9108
+ }
9109
+ }),
9110
+ error
9111
+ );
6278
9112
  }
6279
9113
  }
6280
9114
  const llm = {
6281
9115
  chat: chatWithRecord,
6282
9116
  chatStream: chatStreamWithRecord,
6283
- listModels: () => provider.listModels(),
9117
+ listModels: async () => {
9118
+ try {
9119
+ return await (await currentProvider()).listModels();
9120
+ } catch {
9121
+ return err(
9122
+ HaiAIError.RESOURCE_OPERATION_FAILED,
9123
+ aiM("ai_resourceOperationFailed")
9124
+ );
9125
+ }
9126
+ },
6284
9127
  getHistory,
6285
9128
  listSessions,
6286
9129
  async ask(question, options) {
@@ -6334,7 +9177,15 @@ function createAILLMFunctions(config, deps) {
6334
9177
  const maxRepairs = request.maxRepairs ?? 1;
6335
9178
  let lastError = "";
6336
9179
  for (let attempt = 0; attempt <= maxRepairs; attempt++) {
6337
- const messages = attempt === 0 ? baseMessages : [...baseMessages, { role: "user", content: aiM("ai_generateObjectRepair", { params: { error: lastError } }) }];
9180
+ const messages = attempt === 0 ? baseMessages : [
9181
+ ...baseMessages,
9182
+ {
9183
+ role: "user",
9184
+ content: aiM("ai_generateObjectRepair", {
9185
+ params: { error: lastError }
9186
+ })
9187
+ }
9188
+ ];
6338
9189
  const result = await chatWithRecord({
6339
9190
  messages,
6340
9191
  model: request.model,
@@ -6342,7 +9193,10 @@ function createAILLMFunctions(config, deps) {
6342
9193
  tempModel: request.tempModel,
6343
9194
  enablePersist: false,
6344
9195
  signal: request.signal,
6345
- response_format: { type: "json_schema", json_schema: { name: schemaName, schema: jsonSchema, strict: true } }
9196
+ response_format: {
9197
+ type: "json_schema",
9198
+ json_schema: { name: schemaName, schema: jsonSchema, strict: true }
9199
+ }
6346
9200
  });
6347
9201
  if (!result.success)
6348
9202
  return result;
@@ -6360,7 +9214,12 @@ function createAILLMFunctions(config, deps) {
6360
9214
  return ok(validated.data);
6361
9215
  lastError = validated.error.message;
6362
9216
  }
6363
- return err(HaiAIError.INVALID_REQUEST, aiM("ai_generateObjectFailed", { params: { error: lastError || "unknown" } }));
9217
+ return err(
9218
+ HaiAIError.INVALID_REQUEST,
9219
+ aiM("ai_generateObjectFailed", {
9220
+ params: { error: lastError || "unknown" }
9221
+ })
9222
+ );
6364
9223
  }
6365
9224
  };
6366
9225
  const tools = {
@@ -6498,25 +9357,25 @@ function createAIMCPFunctions() {
6498
9357
  }
6499
9358
  };
6500
9359
  }
6501
- function createScopedMemory(core33, binding) {
9360
+ function createScopedMemory(core32, binding) {
6502
9361
  const { objectId, scope } = binding;
6503
9362
  const accessScope = { objectId, scope };
6504
9363
  return {
6505
- extract: (messages, options) => core33.extract(messages, { ...options, objectId, scope }),
6506
- recall: (query, options) => core33.recall(query, { ...options, objectId, scope }),
6507
- injectMemories: (messages, options) => core33.injectMemories(messages, { ...options, objectId, scope }),
6508
- add: (entry) => core33.add({ ...entry, objectId, scope }),
6509
- update: (memoryId, updates) => core33.update(memoryId, updates, accessScope),
6510
- get: (memoryId) => core33.get(memoryId, accessScope),
6511
- remove: (memoryId) => core33.remove(memoryId, accessScope),
6512
- list: (options) => core33.list({ ...options, objectId, scope }),
6513
- listPage: (options) => core33.listPage({ ...options, objectId, scope }),
6514
- clear: (options) => core33.clear({ ...options, objectId, scope })
9364
+ extract: (messages, options) => core32.extract(messages, { ...options, objectId, scope }),
9365
+ recall: (query, options) => core32.recall(query, { ...options, objectId, scope }),
9366
+ injectMemories: (messages, options) => core32.injectMemories(messages, { ...options, objectId, scope }),
9367
+ add: (entry) => core32.add({ ...entry, objectId, scope }),
9368
+ update: (memoryId, updates) => core32.update(memoryId, updates, accessScope),
9369
+ get: (memoryId) => core32.get(memoryId, accessScope),
9370
+ remove: (memoryId) => core32.remove(memoryId, accessScope),
9371
+ list: (options) => core32.list({ ...options, objectId, scope }),
9372
+ listPage: (options) => core32.listPage({ ...options, objectId, scope }),
9373
+ clear: (options) => core32.clear({ ...options, objectId, scope })
6515
9374
  };
6516
9375
  }
6517
- function createMemoryOperations(core33) {
9376
+ function createMemoryOperations(core32) {
6518
9377
  return {
6519
- ...core33,
9378
+ ...core32,
6520
9379
  /**
6521
9380
  * 清空记忆(空过滤保护)。
6522
9381
  *
@@ -6528,15 +9387,15 @@ function createMemoryOperations(core33) {
6528
9387
  if (!hasFilter) {
6529
9388
  return err(HaiAIError.MEMORY_STORE_FAILED, aiM("ai_memoryClearRequiresFilter"));
6530
9389
  }
6531
- return core33.clear(options);
9390
+ return core32.clear(options);
6532
9391
  },
6533
- scoped: (binding) => createScopedMemory(core33, binding),
9392
+ scoped: (binding) => createScopedMemory(core32, binding),
6534
9393
  admin: {
6535
9394
  async clearAll(options) {
6536
9395
  if (options?.confirm !== true) {
6537
9396
  return err(HaiAIError.MEMORY_STORE_FAILED, aiM("ai_memoryClearAllUnconfirmed"));
6538
9397
  }
6539
- return core33.clear();
9398
+ return core32.clear();
6540
9399
  }
6541
9400
  }
6542
9401
  };
@@ -6887,7 +9746,7 @@ function buildMem0Config(deps) {
6887
9746
  const vectorResolved = buildVectorStoreConfig(deps);
6888
9747
  if (!vectorResolved.success)
6889
9748
  return vectorResolved;
6890
- if (core.module.requiresSharedState(process.env) && !vectorResolved.data.persistent)
9749
+ if (core.module.requiresSharedState(process2.env) && !vectorResolved.data.persistent)
6891
9750
  return err(HaiAIError.CONFIGURATION_ERROR, aiM("ai_memorySharedBackendRequired"));
6892
9751
  return ok({
6893
9752
  vectorProvider: vectorResolved.data.provider,
@@ -7099,16 +9958,16 @@ async function createMem0OssMemoryOperations(deps) {
7099
9958
  const sdk = await import('@qdrant/js-client-rest').catch(() => void 0);
7100
9959
  if (sdk && typeof sdk.QdrantClient?.prototype?.search === "function") {
7101
9960
  const url = deps.vectorBackend?.url ?? "";
7102
- const client = new sdk.QdrantClient({
9961
+ const client2 = new sdk.QdrantClient({
7103
9962
  url,
7104
9963
  port: Number(new URL(url).port) || 6333,
7105
9964
  apiKey: deps.vectorBackend?.apiKey
7106
9965
  });
7107
- const upsert = client.upsert.bind(client);
7108
- const remove = client.delete.bind(client);
7109
- client.upsert = (collection, options) => upsert(collection, { ...options, wait: true });
7110
- client.delete = (collection, options) => remove(collection, { ...options, wait: true });
7111
- configResult.data.config.vectorStore.config.client = client;
9966
+ const upsert = client2.upsert.bind(client2);
9967
+ const remove = client2.delete.bind(client2);
9968
+ client2.upsert = (collection, options) => upsert(collection, { ...options, wait: true });
9969
+ client2.delete = (collection, options) => remove(collection, { ...options, wait: true });
9970
+ configResult.data.config.vectorStore.config.client = client2;
7112
9971
  } else {
7113
9972
  const failure = err(HaiAIError.CONFIGURATION_ERROR, aiM("ai_memoryBackendSdkIncompatible"));
7114
9973
  if (!failure.success)
@@ -8451,7 +11310,7 @@ function createRerankOperations(config) {
8451
11310
  };
8452
11311
  }
8453
11312
  var logger28 = core.logger.child({ module: "ai", scope: "retrieval" });
8454
- function createRetrievalOperations(embeddingOps, storeProvider, sourceStore, rerankOps) {
11313
+ function createRetrievalOperations(embeddingOps, storeProvider, sourceStore, rerankOps, registry) {
8455
11314
  const vectorStoreCache = /* @__PURE__ */ new Map();
8456
11315
  function getVectorStore(collection) {
8457
11316
  let store = vectorStoreCache.get(collection);
@@ -8472,6 +11331,10 @@ function createRetrievalOperations(embeddingOps, storeProvider, sourceStore, rer
8472
11331
  * @returns `ok(undefined)` 注册成功;`err` 含错误码 `RETRIEVAL_FAILED`(重复注册)
8473
11332
  */
8474
11333
  async addSource(source) {
11334
+ if (registry) {
11335
+ const result = await registry.operations.register(source);
11336
+ return result.success ? ok(void 0) : result;
11337
+ }
8475
11338
  const existing = await sourceStore.get(source.id);
8476
11339
  if (existing) {
8477
11340
  return err(HaiAIError.RETRIEVAL_FAILED, aiM("ai_internalError", { params: { error: `Source '${source.id}' already exists` } }));
@@ -8489,6 +11352,8 @@ function createRetrievalOperations(embeddingOps, storeProvider, sourceStore, rer
8489
11352
  * @returns `ok(undefined)` 删除成功;`err` 含错误码 `RETRIEVAL_SOURCE_NOT_FOUND`
8490
11353
  */
8491
11354
  async removeSource(sourceId) {
11355
+ if (registry)
11356
+ return registry.operations.remove(sourceId);
8492
11357
  const existing = await sourceStore.get(sourceId);
8493
11358
  if (!existing) {
8494
11359
  return err(HaiAIError.RETRIEVAL_SOURCE_NOT_FOUND, aiM("ai_internalError", { params: { error: `Source '${sourceId}' not found` } }));
@@ -8503,7 +11368,7 @@ function createRetrievalOperations(embeddingOps, storeProvider, sourceStore, rer
8503
11368
  * @returns 全部检索源数组(无源时返回空数组)
8504
11369
  */
8505
11370
  async listSources() {
8506
- return sourceStore.query({});
11371
+ return registry ? registry.values() : sourceStore.query({});
8507
11372
  },
8508
11373
  /**
8509
11374
  * 执行向量检索
@@ -8518,13 +11383,13 @@ function createRetrievalOperations(embeddingOps, storeProvider, sourceStore, rer
8518
11383
  */
8519
11384
  async retrieve(request) {
8520
11385
  const startTime = Date.now();
8521
- const targetSources = await sourceStore.query(
8522
- request.sources?.length ? { where: { id: { $in: request.sources } } } : {}
8523
- );
8524
- if (targetSources.length === 0) {
8525
- return err(HaiAIError.RETRIEVAL_SOURCE_NOT_FOUND, aiM("ai_internalError", { params: { error: "No retrieval sources configured" } }));
8526
- }
8527
11386
  try {
11387
+ const targetSources = registry ? (await registry.values()).filter((source) => !request.sources?.length || request.sources.includes(source.id)) : await sourceStore.query(
11388
+ request.sources?.length ? { where: { id: { $in: request.sources } } } : {}
11389
+ );
11390
+ if (targetSources.length === 0) {
11391
+ return err(HaiAIError.RETRIEVAL_SOURCE_NOT_FOUND, aiM("ai_internalError", { params: { error: "No retrieval sources configured" } }));
11392
+ }
8528
11393
  const embedResult = await embeddingOps.embedText(request.query);
8529
11394
  if (!embedResult.success) {
8530
11395
  return err(HaiAIError.RETRIEVAL_FAILED, aiM("ai_internalError", { params: { error: "Failed to embed query" } }), embedResult.error);
@@ -8703,38 +11568,207 @@ function createTokenOperations(config) {
8703
11568
  }
8704
11569
 
8705
11570
  // src/ai-functions.ts
8706
- var logger30 = core.logger.child({ module: "ai", scope: "functions" });
8707
11571
  async function createAISubsystems(config, deps) {
8708
11572
  const { storeProvider, datapipe: datapipeDep } = deps;
8709
11573
  const memoryParsed = MemoryConfigSchema.parse(config.memory ?? {});
8710
11574
  const useMem0Oss = memoryParsed.provider === "mem0";
8711
- const chatRecordStore = storeProvider.createRelStore("hai_ai_chat_records", { hasObjectId: true, hasSessionId: true });
8712
- const sessionStore = storeProvider.createRelStore("hai_ai_sessions", { hasObjectId: true });
8713
- const sourceStore = storeProvider.createRelStore("hai_ai_retrieval_sources");
8714
- const memoryStore = useMem0Oss ? null : storeProvider.createRelStore("hai_ai_memory", { hasObjectId: true, hasScopeIndex: true });
8715
- const contextStore = storeProvider.createRelStore("hai_ai_context", { hasObjectId: true, hasSessionId: true });
8716
- const personaStore = storeProvider.createRelStore("hai_ai_persona", { hasObjectId: true });
11575
+ const chatRecordStore = storeProvider.createRelStore(
11576
+ "hai_ai_chat_records",
11577
+ { hasObjectId: true, hasSessionId: true }
11578
+ );
11579
+ const sessionStore = storeProvider.createRelStore(
11580
+ "hai_ai_sessions",
11581
+ { hasObjectId: true }
11582
+ );
11583
+ const sourceStore = storeProvider.createRelStore(
11584
+ "hai_ai_retrieval_sources"
11585
+ );
11586
+ const memoryStore = useMem0Oss ? null : storeProvider.createRelStore("hai_ai_memory", {
11587
+ hasObjectId: true,
11588
+ hasScopeIndex: true
11589
+ });
11590
+ const contextStore = storeProvider.createRelStore(
11591
+ "hai_ai_context",
11592
+ { hasObjectId: true, hasSessionId: true }
11593
+ );
11594
+ const personaStore = storeProvider.createRelStore(
11595
+ "hai_ai_persona",
11596
+ { hasObjectId: true }
11597
+ );
8717
11598
  const a2aStores = config.a2a ? createA2AStores(storeProvider) : null;
8718
11599
  const memoryVectorStore = useMem0Oss ? null : storeProvider.createVectorStore("hai_ai_memory_vectors");
11600
+ let registriesOpen = true;
11601
+ const isOpen = () => registriesOpen;
11602
+ const llmModels = createResourceRegistry(
11603
+ storeProvider.createRelStore("hai_ai_llm_models"),
11604
+ ModelEntrySchema,
11605
+ config.llm.models ?? [],
11606
+ isOpen
11607
+ );
11608
+ const audioModels = createResourceRegistry(
11609
+ storeProvider.createRelStore("hai_ai_audio_models"),
11610
+ AudioModelEntrySchema,
11611
+ config.audio?.models ?? [],
11612
+ isOpen
11613
+ );
11614
+ const imageModels = createResourceRegistry(
11615
+ storeProvider.createRelStore("hai_ai_image_models"),
11616
+ ImageModelEntrySchema,
11617
+ config.image?.models ?? [],
11618
+ isOpen
11619
+ );
11620
+ const mcpServers = createResourceRegistry(
11621
+ storeProvider.createRelStore("hai_ai_mcp_servers"),
11622
+ MCPExternalServerSchema,
11623
+ config.mcp?.servers ?? [],
11624
+ isOpen
11625
+ );
11626
+ const retrievalSources = createResourceRegistry(
11627
+ sourceStore,
11628
+ RetrievalSourceSchema,
11629
+ config.retrieval?.sources ?? [],
11630
+ isOpen
11631
+ );
11632
+ async function llmConfig() {
11633
+ return {
11634
+ ...config,
11635
+ llm: { ...config.llm, models: await llmModels.values() }
11636
+ };
11637
+ }
11638
+ async function audioConfig() {
11639
+ return {
11640
+ ...config,
11641
+ audio: AudioConfigSchema.parse({
11642
+ ...config.audio,
11643
+ models: await audioModels.values()
11644
+ })
11645
+ };
11646
+ }
11647
+ async function imageConfig() {
11648
+ return {
11649
+ ...config,
11650
+ image: ImageConfigSchema.parse({
11651
+ ...config.image,
11652
+ models: await imageModels.values()
11653
+ })
11654
+ };
11655
+ }
11656
+ async function mcpLookup(ids) {
11657
+ const servers = [];
11658
+ for (const id of ids) {
11659
+ const entry = await mcpServers.read(id);
11660
+ if (!entry)
11661
+ throw new Error("MCP resource missing");
11662
+ const server = entry.value;
11663
+ if (server.transport === "stdio") {
11664
+ servers.push({
11665
+ name: server.name,
11666
+ command: server.command,
11667
+ args: server.args,
11668
+ env: Object.entries(resolveEnvironment(server.environmentRefs)).map(
11669
+ ([name, value]) => ({ name, value })
11670
+ )
11671
+ });
11672
+ } else {
11673
+ servers.push({
11674
+ type: server.transport,
11675
+ name: server.name,
11676
+ url: server.url,
11677
+ headers: Object.entries(resolveEnvironment(server.headerEnv)).map(
11678
+ ([name, value]) => ({ name, value })
11679
+ )
11680
+ });
11681
+ }
11682
+ }
11683
+ return servers;
11684
+ }
11685
+ const acp = createACPOperations(
11686
+ storeProvider,
11687
+ config,
11688
+ deps.acpAdapters,
11689
+ llmConfig,
11690
+ mcpLookup
11691
+ );
8719
11692
  await storeProvider.initialize();
8720
- const llmFunctions = createAILLMFunctions(config, { recordStore: chatRecordStore, sessionStore });
8721
- const llm = llmFunctions.llm;
8722
- const mcp = createAIMCPFunctions();
8723
- const embedding = createEmbeddingOperations(config);
8724
- const reasoning = createReasoningOperations(config, llm);
8725
- const rerank = createRerankOperations(config);
8726
- const retrieval = createRetrievalOperations(embedding, storeProvider, sourceStore, rerank);
8727
- if (config.retrieval?.sources?.length) {
8728
- const configSources = RetrievalConfigSchema.parse(config.retrieval).sources ?? [];
8729
- sourceStore.saveMany(configSources.map((s) => ({ id: s.id, data: s }))).catch((e) => logger30.warn("Failed to pre-register retrieval sources", { error: e }));
11693
+ const llmFunctions = createAILLMFunctions(config, {
11694
+ recordStore: chatRecordStore,
11695
+ sessionStore,
11696
+ resolveConfig: llmConfig
11697
+ });
11698
+ const llm = Object.assign(llmFunctions.llm, {
11699
+ models: llmModels.operations
11700
+ });
11701
+ const mcp = Object.assign(createAIMCPFunctions(), {
11702
+ servers: mcpServers.operations
11703
+ });
11704
+ async function resourceCall(operation) {
11705
+ try {
11706
+ return await operation();
11707
+ } catch {
11708
+ return err(
11709
+ HaiAIError.RESOURCE_OPERATION_FAILED,
11710
+ aiM("ai_resourceOperationFailed")
11711
+ );
11712
+ }
8730
11713
  }
11714
+ const embedding = {
11715
+ embed: (request) => resourceCall(
11716
+ async () => createEmbeddingOperations(await llmConfig()).embed(request)
11717
+ ),
11718
+ embedText: (text) => resourceCall(
11719
+ async () => createEmbeddingOperations(await llmConfig()).embedText(text)
11720
+ ),
11721
+ embedBatch: (texts) => resourceCall(
11722
+ async () => createEmbeddingOperations(await llmConfig()).embedBatch(texts)
11723
+ )
11724
+ };
11725
+ const reasoning = {
11726
+ run: (query, options) => resourceCall(async () => createReasoningOperations(await llmConfig(), llm).run(query, options)),
11727
+ async *runStream(query, options) {
11728
+ yield* createReasoningOperations(await llmConfig(), llm).runStream(query, options);
11729
+ }
11730
+ };
11731
+ const rerank = {
11732
+ rerank: (request) => resourceCall(
11733
+ async () => createRerankOperations(await llmConfig()).rerank(request)
11734
+ ),
11735
+ rerankTexts: (query, texts, topN) => resourceCall(
11736
+ async () => createRerankOperations(await llmConfig()).rerankTexts(
11737
+ query,
11738
+ texts,
11739
+ topN
11740
+ )
11741
+ )
11742
+ };
11743
+ const retrieval = Object.assign(
11744
+ createRetrievalOperations(
11745
+ embedding,
11746
+ storeProvider,
11747
+ sourceStore,
11748
+ rerank,
11749
+ retrievalSources
11750
+ ),
11751
+ { sources: retrievalSources.operations }
11752
+ );
8731
11753
  const rag = createRagOperations(llm, retrieval);
8732
11754
  const knowledgeParsed = KnowledgeConfigSchema.parse(config.knowledge ?? {});
8733
11755
  const knowledgeStore = storeProvider.createKnowledgeStore?.();
8734
- const knowledge = createKnowledgeOperations(knowledgeParsed, llm, embedding, datapipeDep, knowledgeStore);
8735
- const memoryCore = memoryStore && memoryVectorStore ? createNativeMemoryOperations(memoryParsed, llm, embedding, memoryStore, memoryVectorStore) : await createMem0OssMemoryOperations({
11756
+ const knowledge = createKnowledgeOperations(
11757
+ knowledgeParsed,
11758
+ llm,
11759
+ embedding,
11760
+ datapipeDep,
11761
+ knowledgeStore
11762
+ );
11763
+ const memoryCore = memoryStore && memoryVectorStore ? createNativeMemoryOperations(
11764
+ memoryParsed,
11765
+ llm,
11766
+ embedding,
11767
+ memoryStore,
11768
+ memoryVectorStore
11769
+ ) : await createMem0OssMemoryOperations({
8736
11770
  config: memoryParsed,
8737
- aiConfig: config,
11771
+ aiConfig: await llmConfig(),
8738
11772
  llm,
8739
11773
  collectionName: "hai_ai_memory",
8740
11774
  embeddingDims: config.embedding?.dimensions,
@@ -8745,20 +11779,109 @@ async function createAISubsystems(config, deps) {
8745
11779
  const summaryParsed = SummaryConfigSchema.parse(config.summary ?? {});
8746
11780
  const compressParsed = CompressConfigSchema.parse(config.compress ?? {});
8747
11781
  const token = createTokenOperations(tokenParsed);
8748
- const summary = createSummaryOperations(config.llm, llm, token, summaryParsed);
8749
- const compress = createCompressOperations(compressParsed, token, summary, config.llm.maxTokens ?? 4096);
8750
- const context = createContextOperations(compressParsed, token, compress, contextStore, sessionStore, {
8751
- llm,
8752
- memory: memory ?? void 0,
8753
- rag: rag ?? void 0,
8754
- reasoning: reasoning ?? void 0,
8755
- summary
8756
- });
11782
+ const summary = {
11783
+ generate: (messages, options) => resourceCall(async () => createSummaryOperations((await llmConfig()).llm, llm, token, summaryParsed).generate(messages, options)),
11784
+ summarize: (messages, options) => resourceCall(async () => createSummaryOperations((await llmConfig()).llm, llm, token, summaryParsed).summarize(messages, options))
11785
+ };
11786
+ const compress = createCompressOperations(
11787
+ compressParsed,
11788
+ token,
11789
+ summary,
11790
+ config.llm.maxTokens ?? 4096
11791
+ );
11792
+ const context = createContextOperations(
11793
+ compressParsed,
11794
+ token,
11795
+ compress,
11796
+ contextStore,
11797
+ sessionStore,
11798
+ {
11799
+ llm,
11800
+ memory: memory ?? void 0,
11801
+ rag: rag ?? void 0,
11802
+ reasoning: reasoning ?? void 0,
11803
+ summary
11804
+ }
11805
+ );
8757
11806
  const persona = createPersonaOperations(personaStore);
8758
- const file = createFileOperations(config, llm);
8759
- const audio = createAudioOperations(config);
8760
- const image = createImageOperations(config);
8761
- return { a2aStores, llm, mcp, embedding, reasoning, rerank, retrieval, rag, knowledge, memory, persona, token, summary, compress, context, file, audio, image };
11807
+ const file = {
11808
+ parse: (request) => resourceCall(async () => createFileOperations(await llmConfig(), llm).parse(request)),
11809
+ parseText: (content, filename) => resourceCall(async () => createFileOperations(await llmConfig(), llm).parseText(content, filename))
11810
+ };
11811
+ let audioSnapshot = createAudioOperations(await audioConfig());
11812
+ async function currentAudio2() {
11813
+ audioSnapshot = createAudioOperations(await audioConfig());
11814
+ return audioSnapshot;
11815
+ }
11816
+ const audio = {
11817
+ models: {
11818
+ list: () => resourceCall(async () => {
11819
+ await currentAudio2();
11820
+ return audioModels.operations.list();
11821
+ }),
11822
+ get: (id) => resourceCall(async () => {
11823
+ await currentAudio2();
11824
+ return audioModels.operations.get(id);
11825
+ }),
11826
+ register: (input) => resourceCall(async () => {
11827
+ const result = await audioModels.operations.register(input);
11828
+ if (result.success)
11829
+ await currentAudio2();
11830
+ return result;
11831
+ }),
11832
+ update: (input, version) => resourceCall(async () => {
11833
+ const result = await audioModels.operations.update(input, version);
11834
+ if (result.success)
11835
+ await currentAudio2();
11836
+ return result;
11837
+ }),
11838
+ remove: (id, version) => resourceCall(async () => {
11839
+ const result = await audioModels.operations.remove(id, version);
11840
+ if (result.success)
11841
+ await currentAudio2();
11842
+ return result;
11843
+ })
11844
+ },
11845
+ transcribe: (request) => resourceCall(async () => (await currentAudio2()).transcribe(request)),
11846
+ synthesize: (request) => resourceCall(async () => (await currentAudio2()).synthesize(request)),
11847
+ async *transcribeStream(request) {
11848
+ yield* (await currentAudio2()).transcribeStream(request);
11849
+ },
11850
+ async *synthesizeStream(request) {
11851
+ yield* (await currentAudio2()).synthesizeStream(request);
11852
+ },
11853
+ getCapabilities: (request) => audioSnapshot.getCapabilities(request)
11854
+ };
11855
+ const image = {
11856
+ models: imageModels.operations,
11857
+ generate: (request) => resourceCall(
11858
+ async () => createImageOperations(await imageConfig()).generate(request)
11859
+ )
11860
+ };
11861
+ return {
11862
+ closeRegistries: () => {
11863
+ registriesOpen = false;
11864
+ },
11865
+ a2aStores,
11866
+ llm,
11867
+ acp,
11868
+ mcp,
11869
+ embedding,
11870
+ reasoning,
11871
+ rerank,
11872
+ retrieval,
11873
+ rag,
11874
+ knowledge,
11875
+ memory,
11876
+ persona,
11877
+ token,
11878
+ summary,
11879
+ compress,
11880
+ context,
11881
+ file,
11882
+ audio,
11883
+ image
11884
+ };
8762
11885
  }
8763
11886
  var WRITE_LEASE_MS = 3e5;
8764
11887
  var CAS_ATTEMPTS = 12;
@@ -9077,7 +12200,7 @@ function documentInfo(document) {
9077
12200
  }
9078
12201
 
9079
12202
  // src/store/providers/ai-store-provider-db.ts
9080
- var logger31 = core.logger.child({ module: "ai", scope: "store-provider-db" });
12203
+ var logger30 = core.logger.child({ module: "ai", scope: "store-provider-db" });
9081
12204
  function requireSqlSuccess(result) {
9082
12205
  if (!result.success)
9083
12206
  throw new Error(result.error.message, { cause: result.error });
@@ -9596,7 +12719,7 @@ function createDbStoreProvider(deps) {
9596
12719
  if (result.status === "rejected")
9597
12720
  throw result.reason;
9598
12721
  }
9599
- logger31.debug("DB store provider initialized", { tableCount: relStores.size });
12722
+ logger30.debug("DB store provider initialized", { tableCount: relStores.size });
9600
12723
  }
9601
12724
  },
9602
12725
  async close() {
@@ -9827,10 +12950,11 @@ function createMemoryStoreProvider() {
9827
12950
  }
9828
12951
 
9829
12952
  // src/ai-main.ts
9830
- var logger32 = core.logger.child({ module: "ai", scope: "main" });
12953
+ var logger31 = core.logger.child({ module: "ai", scope: "main" });
9831
12954
  var initInProgress = false;
9832
12955
  var currentConfig = null;
9833
12956
  var currentLLM = null;
12957
+ var currentACP = null;
9834
12958
  var currentMCP = null;
9835
12959
  var currentEmbedding = null;
9836
12960
  var currentReasoning = null;
@@ -9850,12 +12974,14 @@ var currentImage = null;
9850
12974
  var currentA2AConfig;
9851
12975
  var currentA2AImpl = null;
9852
12976
  var currentA2AStores = null;
12977
+ var currentRegistriesClose = null;
9853
12978
  var currentStoreProvider = null;
9854
12979
  var notInitialized = core.module.createNotInitializedKit(
9855
12980
  HaiAIError.NOT_INITIALIZED,
9856
12981
  () => aiM("ai_notInitialized")
9857
12982
  );
9858
12983
  var notInitializedLLM = {
12984
+ models: notInitialized.proxy(),
9859
12985
  chat: () => Promise.resolve(notInitialized.result()),
9860
12986
  async *chatStream() {
9861
12987
  throw notInitialized.error();
@@ -9869,7 +12995,16 @@ var notInitializedLLM = {
9869
12995
  },
9870
12996
  generateObject: () => Promise.resolve(notInitialized.result())
9871
12997
  };
12998
+ var notInitializedACP = {
12999
+ agents: notInitialized.proxy(),
13000
+ sessions: notInitialized.proxy(),
13001
+ workspaces: notInitialized.proxy(),
13002
+ connect: () => Promise.resolve(notInitialized.result()),
13003
+ close: async () => {
13004
+ }
13005
+ };
9872
13006
  var notInitializedMCP = {
13007
+ servers: notInitialized.proxy(),
9873
13008
  registerTool: () => notInitialized.result(),
9874
13009
  registerResource: () => notInitialized.result(),
9875
13010
  registerPrompt: () => notInitialized.result(),
@@ -9885,6 +13020,7 @@ var notInitializedReasoning = {
9885
13020
  }
9886
13021
  };
9887
13022
  var notInitializedRetrieval = {
13023
+ sources: notInitialized.proxy(),
9888
13024
  addSource: () => Promise.resolve(notInitialized.result()),
9889
13025
  removeSource: () => Promise.resolve(notInitialized.result()),
9890
13026
  listSources: () => Promise.resolve([]),
@@ -9919,6 +13055,7 @@ var notInitializedContext = {
9919
13055
  var notInitializedRerank = notInitialized.proxy();
9920
13056
  var notInitializedFile = notInitialized.proxy();
9921
13057
  var notInitializedAudio = {
13058
+ models: notInitialized.proxy(),
9922
13059
  transcribe: () => Promise.resolve(notInitialized.result()),
9923
13060
  async *transcribeStream() {
9924
13061
  throw notInitialized.error();
@@ -9930,6 +13067,7 @@ var notInitializedAudio = {
9930
13067
  getCapabilities: () => notInitialized.result()
9931
13068
  };
9932
13069
  var notInitializedImage = {
13070
+ models: notInitialized.proxy(),
9933
13071
  generate: () => Promise.resolve(notInitialized.result())
9934
13072
  };
9935
13073
  var a2aLazyOperations = createA2ALazyProxy({
@@ -9945,7 +13083,11 @@ var a2aLazyOperations = createA2ALazyProxy({
9945
13083
  async function resetAllState() {
9946
13084
  const storeProvider = currentStoreProvider;
9947
13085
  await currentA2AImpl?.close();
13086
+ await currentACP?.close();
9948
13087
  currentLLM = null;
13088
+ currentRegistriesClose?.();
13089
+ currentRegistriesClose = null;
13090
+ currentACP = null;
9949
13091
  currentMCP = null;
9950
13092
  currentEmbedding = null;
9951
13093
  currentReasoning = null;
@@ -9971,7 +13113,7 @@ async function resetAllState() {
9971
13113
  try {
9972
13114
  await storeProvider.close();
9973
13115
  } catch (error) {
9974
- logger32.error("AI store provider close failed", { error });
13116
+ logger31.error("AI store provider close failed", { error });
9975
13117
  }
9976
13118
  }
9977
13119
  }
@@ -9987,22 +13129,25 @@ var streamOperations = {
9987
13129
  };
9988
13130
  var ai = {
9989
13131
  async init(config, options) {
9990
- if (Object.keys(process.env).some((name) => /^HAI_AI_LLM_(?:MODELS_\d+_)?API$/.test(name)))
13132
+ if (Object.keys(process2.env).some((name) => /^HAI_AI_LLM_(?:MODELS_\d+_)?API$/.test(name)))
9991
13133
  return err(HaiAIError.CONFIGURATION_ERROR, aiM("ai_llmProtocolRenamed"));
9992
13134
  if (initInProgress) {
9993
- logger32.warn("AI init already in progress, skipping concurrent call");
13135
+ logger31.warn("AI init already in progress, skipping concurrent call");
9994
13136
  return err(HaiAIError.INIT_IN_PROGRESS, aiM("ai_initInProgress"));
9995
13137
  }
9996
13138
  initInProgress = true;
13139
+ let closingPrevious = false;
9997
13140
  try {
9998
13141
  if (currentConfig) {
9999
- logger32.warn("AI module is already initialized, reinitializing");
13142
+ logger31.warn("AI module is already initialized, reinitializing");
13143
+ closingPrevious = true;
10000
13144
  await ai.close();
13145
+ closingPrevious = false;
10001
13146
  }
10002
- logger32.info("Initializing AI module");
13147
+ logger31.info("Initializing AI module");
10003
13148
  const parseResult = AIConfigSchema.safeParse(config ?? {});
10004
13149
  if (!parseResult.success) {
10005
- logger32.error("AI config validation failed", { error: parseResult.error.message });
13150
+ logger31.error("AI config validation failed", { error: parseResult.error.message });
10006
13151
  return err(
10007
13152
  HaiAIError.CONFIGURATION_ERROR,
10008
13153
  aiM("ai_configError", { params: { error: parseResult.error.message } }),
@@ -10017,16 +13162,19 @@ var ai = {
10017
13162
  storeProvider = createDbStoreProviderFromModules();
10018
13163
  } else {
10019
13164
  storeProvider = createMemoryStoreProvider();
10020
- logger32.warn("AI is using the ephemeral in-memory store; initialize reldb and vecdb before ai.init() for persistent data");
13165
+ logger31.warn("AI is using the ephemeral in-memory store; initialize reldb and vecdb before ai.init() for persistent data");
10021
13166
  }
10022
- if (core.module.requiresSharedState(process.env) && storeProvider.stateScope !== "shared")
13167
+ if (core.module.requiresSharedState(process2.env) && storeProvider.stateScope !== "shared")
10023
13168
  return err(HaiAIError.CONFIGURATION_ERROR, aiM("ai_sharedBackendRequired"));
10024
13169
  currentStoreProvider = storeProvider;
10025
13170
  const subs = await createAISubsystems(parsed, {
10026
13171
  storeProvider,
10027
- datapipe
13172
+ datapipe,
13173
+ acpAdapters: options?.acpAdapters
10028
13174
  });
13175
+ currentRegistriesClose = subs.closeRegistries;
10029
13176
  currentLLM = subs.llm;
13177
+ currentACP = subs.acp;
10030
13178
  currentMCP = subs.mcp;
10031
13179
  currentEmbedding = subs.embedding;
10032
13180
  currentReasoning = subs.reasoning;
@@ -10046,11 +13194,17 @@ var ai = {
10046
13194
  currentA2AConfig = parsed.a2a;
10047
13195
  currentA2AStores = subs.a2aStores;
10048
13196
  currentConfig = parsed;
10049
- logger32.info("AI module initialized", { model: parsed.llm?.model });
13197
+ logger31.info("AI module initialized", { model: parsed.llm?.model });
10050
13198
  return ok(void 0);
10051
13199
  } catch (error) {
10052
- await resetAllState();
10053
- logger32.error("AI module initialization failed", { error });
13200
+ if (!closingPrevious) {
13201
+ try {
13202
+ await resetAllState();
13203
+ } catch (cleanupError) {
13204
+ logger31.error("AI initialization cleanup failed", { error: cleanupError });
13205
+ }
13206
+ }
13207
+ logger31.error("AI module initialization failed", { error });
10054
13208
  return err(
10055
13209
  HaiAIError.CONFIGURATION_ERROR,
10056
13210
  aiM("ai_initFailed", {
@@ -10065,6 +13219,9 @@ var ai = {
10065
13219
  get llm() {
10066
13220
  return currentLLM ?? notInitializedLLM;
10067
13221
  },
13222
+ get acp() {
13223
+ return currentACP ?? notInitializedACP;
13224
+ },
10068
13225
  get mcp() {
10069
13226
  return currentMCP ?? notInitializedMCP;
10070
13227
  },
@@ -10130,12 +13287,12 @@ var ai = {
10130
13287
  },
10131
13288
  async close() {
10132
13289
  if (!currentConfig) {
10133
- logger32.info("AI module already closed, skipping");
13290
+ logger31.info("AI module already closed, skipping");
10134
13291
  return;
10135
13292
  }
10136
- logger32.info("Closing AI module");
13293
+ logger31.info("Closing AI module");
10137
13294
  await resetAllState();
10138
- logger32.info("AI module closed");
13295
+ logger31.info("AI module closed");
10139
13296
  }
10140
13297
  };
10141
13298
  function createMcpServer(options) {