dsh-lark-bot 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -584,7 +584,8 @@ var init_acp_adapter = __esm({
584
584
  });
585
585
 
586
586
  // src/cli.ts
587
- import { readFileSync } from "fs";
587
+ import { readFileSync, realpathSync as realpathSync2 } from "fs";
588
+ import { fileURLToPath as fileURLToPath2 } from "url";
588
589
  import { Command } from "commander";
589
590
 
590
591
  // src/cli/commands/doctor.ts
@@ -1521,9 +1522,9 @@ async function writeFileAtomic(target, data, options = {}) {
1521
1522
  }
1522
1523
  }
1523
1524
  async function rmSilently(path) {
1524
- const { rm: rm3 } = await import("fs/promises");
1525
+ const { rm: rm4 } = await import("fs/promises");
1525
1526
  try {
1526
- await rm3(path, { force: true });
1527
+ await rm4(path, { force: true });
1527
1528
  } catch {
1528
1529
  }
1529
1530
  }
@@ -1695,7 +1696,7 @@ async function runDoctor(options) {
1695
1696
  }
1696
1697
 
1697
1698
  // src/cli/commands/run.ts
1698
- import { mkdir as mkdir7 } from "fs/promises";
1699
+ import { mkdir as mkdir8 } from "fs/promises";
1699
1700
 
1700
1701
  // src/bot/active-runs.ts
1701
1702
  var ActiveRuns = class {
@@ -1878,6 +1879,20 @@ var DensityStore = class {
1878
1879
  }
1879
1880
  };
1880
1881
 
1882
+ // src/bot/model-store.ts
1883
+ var ModelStore = class {
1884
+ overrides = /* @__PURE__ */ new Map();
1885
+ get(scope) {
1886
+ return this.overrides.get(scope);
1887
+ }
1888
+ set(scope, model) {
1889
+ this.overrides.set(scope, model);
1890
+ }
1891
+ clear(scope) {
1892
+ return this.overrides.delete(scope);
1893
+ }
1894
+ };
1895
+
1881
1896
  // src/bot/pending-queue.ts
1882
1897
  var PendingQueue = class {
1883
1898
  constructor(quietMs, onFlush) {
@@ -2636,6 +2651,749 @@ function errorMessage(error) {
2636
2651
  return error instanceof Error ? error.message : String(error);
2637
2652
  }
2638
2653
 
2654
+ // src/config/dsh-config.ts
2655
+ import { mkdir as mkdir5, readFile as readFile2, rm, writeFile as writeFile4 } from "fs/promises";
2656
+ import { homedir as homedir5 } from "os";
2657
+ import { dirname as dirname3, join as join7 } from "path";
2658
+ import { Document, parseDocument } from "yaml";
2659
+ init_dsh_runtime();
2660
+ var DEEPSEEK_NAMESPACE = "llm-deepseek";
2661
+ var DEEPSEEK_PROVIDER = "deepseek-official";
2662
+ var DEEPSEEK_DEFAULT_API_KEY_ENV = "DEEPSEEK_API_KEY";
2663
+ var PIAI_NAMESPACE = "llm-pi-ai";
2664
+ var AGENT_DEFAULT_MODEL_NAMESPACE = "agent-default-model";
2665
+ var SUPPORTED_PI_AI_PROTOCOLS = [
2666
+ "openai-completions",
2667
+ "openai-responses",
2668
+ "anthropic-messages"
2669
+ ];
2670
+ var PROVIDER_ID_PATTERN = /^[a-z][a-z0-9._-]*$/;
2671
+ var CREDENTIAL_REF_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
2672
+ var DEFAULT_DEEPSEEK_MODELS = [
2673
+ { id: "deepseek-v4-flash", name: "DeepSeek-V4-Flash", contextWindow: void 0, maxTokens: void 0 },
2674
+ { id: "deepseek-v4-pro", name: "DeepSeek-V4-Pro", contextWindow: void 0, maxTokens: void 0 }
2675
+ ];
2676
+ var LOCK_TIMEOUT_MS = 1e4;
2677
+ function isMapLike(value) {
2678
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2679
+ }
2680
+ function deepEqualJson(a, b) {
2681
+ return JSON.stringify(a) === JSON.stringify(b);
2682
+ }
2683
+ function parseModels(value) {
2684
+ if (!Array.isArray(value)) return void 0;
2685
+ const models = [];
2686
+ for (const raw of value) {
2687
+ if (!isMapLike(raw) || typeof raw.id !== "string" || raw.id.length === 0) continue;
2688
+ models.push({
2689
+ id: raw.id,
2690
+ name: typeof raw.name === "string" ? raw.name : void 0,
2691
+ contextWindow: typeof raw.contextWindow === "number" ? raw.contextWindow : void 0,
2692
+ maxTokens: typeof raw.maxTokens === "number" ? raw.maxTokens : void 0
2693
+ });
2694
+ }
2695
+ return models;
2696
+ }
2697
+ function rawModels(value) {
2698
+ if (!Array.isArray(value)) return [];
2699
+ return value.filter((entry) => isMapLike(entry));
2700
+ }
2701
+ function modelRecord(input) {
2702
+ const record = { id: input.id };
2703
+ if (input.name !== void 0) record.name = input.name;
2704
+ if (input.contextWindow !== void 0) record.contextWindow = input.contextWindow;
2705
+ if (input.maxTokens !== void 0) record.maxTokens = input.maxTokens;
2706
+ return record;
2707
+ }
2708
+ function validateProviderId(id) {
2709
+ if (!PROVIDER_ID_PATTERN.test(id)) {
2710
+ throw new Error(`provider id \u5FC5\u987B\u662F\u5C0F\u5199\u5B57\u6BCD\u5F00\u5934\u4E14\u4EC5\u542B a-z 0-9 . _ -\uFF0C\u5F97\u5230 "${id}"`);
2711
+ }
2712
+ }
2713
+ function validateBaseUrl(url) {
2714
+ let parsed;
2715
+ try {
2716
+ parsed = new URL(url);
2717
+ } catch {
2718
+ throw new Error(`baseURL \u4E0D\u662F\u5408\u6CD5 URL\uFF1A${url}`);
2719
+ }
2720
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
2721
+ throw new Error(`baseURL \u4EC5\u652F\u6301 http/https\uFF1A${url}`);
2722
+ }
2723
+ }
2724
+ function assertProtocol(api) {
2725
+ if (!SUPPORTED_PI_AI_PROTOCOLS.includes(api)) {
2726
+ throw new Error(`\u4E0D\u652F\u6301\u7684 API \u534F\u8BAE "${api}"\uFF0C\u53EF\u9009\uFF1A${SUPPORTED_PI_AI_PROTOCOLS.join(" / ")}`);
2727
+ }
2728
+ }
2729
+ function patchNode(document, path, current, next) {
2730
+ if (isMapLike(current) && isMapLike(next)) {
2731
+ for (const key of Object.keys(current)) {
2732
+ if (!(key in next)) document.deleteIn([...path, key]);
2733
+ }
2734
+ for (const [key, value] of Object.entries(next)) {
2735
+ patchNode(document, [...path, key], current[key], value);
2736
+ }
2737
+ return;
2738
+ }
2739
+ if (!deepEqualJson(current, next)) document.setIn([...path], next);
2740
+ }
2741
+ function parseYamlMap(text, filename) {
2742
+ if (text === void 0 || text.trim().length === 0) return {};
2743
+ const document = parseDocument(text, { prettyErrors: true });
2744
+ if (document.errors.length > 0) {
2745
+ throw new Error(`invalid dsh config at ${filename}: ${document.errors.map((e) => e.message).join("; ")}`);
2746
+ }
2747
+ const root = document.toJS() ?? {};
2748
+ if (!isMapLike(root)) {
2749
+ throw new TypeError(`dsh config ${filename} must be a mapping`);
2750
+ }
2751
+ return root;
2752
+ }
2753
+ async function readOptional(file) {
2754
+ try {
2755
+ return await readFile2(file, "utf8");
2756
+ } catch (error) {
2757
+ if (error.code === "ENOENT") return void 0;
2758
+ throw error;
2759
+ }
2760
+ }
2761
+ function sleep(ms) {
2762
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
2763
+ }
2764
+ async function withFileLock(filename, operation) {
2765
+ const lockPath = `${filename}.lock`;
2766
+ const deadline = Date.now() + LOCK_TIMEOUT_MS;
2767
+ for (; ; ) {
2768
+ try {
2769
+ await writeFile4(lockPath, `${process.pid}
2770
+ `, { flag: "wx" });
2771
+ break;
2772
+ } catch (error) {
2773
+ if (error.code !== "EEXIST") throw error;
2774
+ if (Date.now() >= deadline) {
2775
+ throw new Error(`dsh config lock timed out at ${lockPath}`);
2776
+ }
2777
+ await sleep(80 + Math.random() * 120);
2778
+ }
2779
+ }
2780
+ try {
2781
+ return await operation();
2782
+ } finally {
2783
+ await rm(lockPath, { force: true }).catch(() => void 0);
2784
+ }
2785
+ }
2786
+ var DshProviderManager = class {
2787
+ home;
2788
+ env;
2789
+ settingsFile;
2790
+ credentialsFile;
2791
+ constructor(options = {}) {
2792
+ this.home = options.home ?? homedir5();
2793
+ this.env = options.env ?? process.env;
2794
+ const dshHome = resolveDshHome(this.home, this.env);
2795
+ this.settingsFile = options.settingsFile ?? join7(dshHome, "settings.yaml");
2796
+ this.credentialsFile = options.credentialsFile ?? join7(dshHome, ".credentials.yaml");
2797
+ }
2798
+ async readSettings() {
2799
+ return parseYamlMap(await readOptional(this.settingsFile), this.settingsFile);
2800
+ }
2801
+ async readCredentials() {
2802
+ const root = parseYamlMap(await readOptional(this.credentialsFile), this.credentialsFile);
2803
+ const entries = {};
2804
+ for (const [key, value] of Object.entries(root)) {
2805
+ if (typeof value === "string" && value.length > 0) entries[key] = value;
2806
+ }
2807
+ return entries;
2808
+ }
2809
+ async hasCredential(ref) {
2810
+ const entries = await this.readCredentials();
2811
+ if (entries[ref]) return true;
2812
+ return Boolean(this.env[ref]?.trim());
2813
+ }
2814
+ async listCredentialRefs() {
2815
+ return Object.keys(await this.readCredentials());
2816
+ }
2817
+ async listProviders() {
2818
+ const settings = await this.readSettings();
2819
+ return [await this.describeDeepseek(settings), ...await this.describePiAi(settings)];
2820
+ }
2821
+ async describeDeepseek(settings) {
2822
+ const deepseek = isMapLike(settings[DEEPSEEK_NAMESPACE]) ? settings[DEEPSEEK_NAMESPACE] : {};
2823
+ const credentialRef = typeof deepseek.apiKeyEnv === "string" && deepseek.apiKeyEnv.length > 0 ? deepseek.apiKeyEnv : DEEPSEEK_DEFAULT_API_KEY_ENV;
2824
+ return {
2825
+ id: DEEPSEEK_PROVIDER,
2826
+ displayName: "DeepSeek",
2827
+ namespace: DEEPSEEK_NAMESPACE,
2828
+ configured: Object.keys(deepseek).length > 0,
2829
+ credentialRef,
2830
+ credentialReady: await this.hasCredential(credentialRef),
2831
+ models: parseModels(deepseek.models) ?? DEFAULT_DEEPSEEK_MODELS,
2832
+ managed: true
2833
+ };
2834
+ }
2835
+ async describePiAi(settings) {
2836
+ const piAi = isMapLike(settings[PIAI_NAMESPACE]) ? settings[PIAI_NAMESPACE] : {};
2837
+ const providers = isMapLike(piAi.providers) ? piAi.providers : {};
2838
+ const summaries = [];
2839
+ for (const [id, raw] of Object.entries(providers)) {
2840
+ const profile = isMapLike(raw) ? raw : {};
2841
+ const ref = typeof profile.apiKeyEnv === "string" && profile.apiKeyEnv.length > 0 ? profile.apiKeyEnv : void 0;
2842
+ summaries.push({
2843
+ id,
2844
+ displayName: typeof profile.displayName === "string" ? profile.displayName : id,
2845
+ namespace: PIAI_NAMESPACE,
2846
+ configured: true,
2847
+ credentialRef: ref,
2848
+ credentialReady: ref === void 0 ? false : await this.hasCredential(ref),
2849
+ models: parseModels(profile.models) ?? [],
2850
+ managed: true
2851
+ });
2852
+ }
2853
+ return summaries;
2854
+ }
2855
+ async defaultModel() {
2856
+ const settings = await this.readSettings();
2857
+ const section = settings[AGENT_DEFAULT_MODEL_NAMESPACE];
2858
+ return isMapLike(section) && typeof section.model === "string" ? section.model : void 0;
2859
+ }
2860
+ async setDefaultModel(model) {
2861
+ if (!model.trim()) throw new Error("\u9ED8\u8BA4\u6A21\u578B\u4E0D\u80FD\u4E3A\u7A7A");
2862
+ await this.writeNamespace(AGENT_DEFAULT_MODEL_NAMESPACE, { model });
2863
+ }
2864
+ async upsertDeepseekProvider(input) {
2865
+ const settings = await this.readSettings();
2866
+ const current = isMapLike(settings[DEEPSEEK_NAMESPACE]) ? settings[DEEPSEEK_NAMESPACE] : {};
2867
+ const section = { ...current };
2868
+ if (input.baseURL !== void 0) {
2869
+ validateBaseUrl(input.baseURL);
2870
+ section.baseURL = input.baseURL;
2871
+ }
2872
+ if (input.apiKeyEnv !== void 0) section.apiKeyEnv = input.apiKeyEnv;
2873
+ if (input.apiKey !== void 0) {
2874
+ const ref = typeof section.apiKeyEnv === "string" && section.apiKeyEnv.length > 0 ? section.apiKeyEnv : DEEPSEEK_DEFAULT_API_KEY_ENV;
2875
+ await this.setCredential(ref, input.apiKey);
2876
+ }
2877
+ await this.writeNamespace(DEEPSEEK_NAMESPACE, section);
2878
+ }
2879
+ async removeDeepseekProvider() {
2880
+ const settings = await this.readSettings();
2881
+ const section = isMapLike(settings[DEEPSEEK_NAMESPACE]) ? settings[DEEPSEEK_NAMESPACE] : {};
2882
+ await this.deleteNamespace(DEEPSEEK_NAMESPACE);
2883
+ await this.removeCredential(
2884
+ typeof section.apiKeyEnv === "string" && section.apiKeyEnv.length > 0 ? section.apiKeyEnv : DEEPSEEK_DEFAULT_API_KEY_ENV
2885
+ );
2886
+ }
2887
+ async addDeepseekModel(input) {
2888
+ const settings = await this.readSettings();
2889
+ const current = isMapLike(settings[DEEPSEEK_NAMESPACE]) ? settings[DEEPSEEK_NAMESPACE] : {};
2890
+ const models = rawModels(current.models).length > 0 ? rawModels(current.models) : DEFAULT_DEEPSEEK_MODELS.map((model) => modelRecord(model));
2891
+ if (models.some((model) => model.id === input.id)) {
2892
+ throw new Error(`\u6A21\u578B ${input.id} \u5DF2\u5B58\u5728\u4E8E ${DEEPSEEK_PROVIDER}`);
2893
+ }
2894
+ await this.writeNamespace(DEEPSEEK_NAMESPACE, {
2895
+ ...current,
2896
+ models: [...models, modelRecord(input)]
2897
+ });
2898
+ }
2899
+ async removeDeepseekModel(id) {
2900
+ const settings = await this.readSettings();
2901
+ const current = isMapLike(settings[DEEPSEEK_NAMESPACE]) ? settings[DEEPSEEK_NAMESPACE] : {};
2902
+ const models = rawModels(current.models);
2903
+ if (models.length === 0) return false;
2904
+ const next = models.filter((model) => model.id !== id);
2905
+ if (next.length === models.length) return false;
2906
+ await this.writeNamespace(DEEPSEEK_NAMESPACE, { ...current, models: next });
2907
+ return true;
2908
+ }
2909
+ async upsertPiAiProvider(input) {
2910
+ validateProviderId(input.id);
2911
+ const settings = await this.readSettings();
2912
+ const piAi = isMapLike(settings[PIAI_NAMESPACE]) ? settings[PIAI_NAMESPACE] : {};
2913
+ const providers = isMapLike(piAi.providers) ? piAi.providers : {};
2914
+ const existing = isMapLike(providers[input.id]) ? providers[input.id] : void 0;
2915
+ const section = existing === void 0 ? {} : { ...existing };
2916
+ if (input.displayName !== void 0) section.displayName = input.displayName;
2917
+ if (input.apiKeyEnv !== void 0) section.apiKeyEnv = input.apiKeyEnv;
2918
+ if (input.api !== void 0) {
2919
+ assertProtocol(input.api);
2920
+ section.api = input.api;
2921
+ }
2922
+ if (input.baseURL !== void 0) {
2923
+ validateBaseUrl(input.baseURL);
2924
+ section.baseURL = input.baseURL;
2925
+ }
2926
+ if (input.models !== void 0) section.models = input.models.map(modelRecord);
2927
+ if (existing === void 0) {
2928
+ if (typeof section.api !== "string") {
2929
+ throw new Error("\u65B0\u589E\u81EA\u5B9A\u4E49 provider \u5FC5\u987B\u6307\u5B9A --api\uFF08openai-completions / openai-responses / anthropic-messages\uFF09");
2930
+ }
2931
+ assertProtocol(section.api);
2932
+ if (typeof section.baseURL !== "string") {
2933
+ throw new Error("\u65B0\u589E\u81EA\u5B9A\u4E49 provider \u5FC5\u987B\u6307\u5B9A --base-url");
2934
+ }
2935
+ validateBaseUrl(section.baseURL);
2936
+ const models = rawModels(section.models);
2937
+ if (models.length === 0) {
2938
+ throw new Error("\u65B0\u589E\u81EA\u5B9A\u4E49 provider \u81F3\u5C11\u9700\u8981\u4E00\u4E2A --model\uFF08models \u4E0D\u80FD\u4E3A\u7A7A\uFF09");
2939
+ }
2940
+ for (const model of models) {
2941
+ if (typeof model.id !== "string" || model.id.length === 0) {
2942
+ throw new Error(`--model \u7F3A\u5C11\u6709\u6548 id\uFF1A${JSON.stringify(model)}`);
2943
+ }
2944
+ }
2945
+ }
2946
+ await this.writeNamespace(PIAI_NAMESPACE, {
2947
+ ...piAi,
2948
+ providers: { ...providers, [input.id]: section }
2949
+ });
2950
+ }
2951
+ async removePiAiProvider(id) {
2952
+ const settings = await this.readSettings();
2953
+ const piAi = isMapLike(settings[PIAI_NAMESPACE]) ? settings[PIAI_NAMESPACE] : {};
2954
+ const providers = isMapLike(piAi.providers) ? piAi.providers : {};
2955
+ if (!(id in providers)) return false;
2956
+ const next = {};
2957
+ for (const [key, value] of Object.entries(providers)) {
2958
+ if (key !== id) next[key] = value;
2959
+ }
2960
+ if (Object.keys(next).length === 0) {
2961
+ await this.deleteNamespace(PIAI_NAMESPACE);
2962
+ } else {
2963
+ await this.writeNamespace(PIAI_NAMESPACE, { ...piAi, providers: next });
2964
+ }
2965
+ return true;
2966
+ }
2967
+ async addPiAiModel(providerId, input) {
2968
+ validateProviderId(providerId);
2969
+ const settings = await this.readSettings();
2970
+ const piAi = isMapLike(settings[PIAI_NAMESPACE]) ? settings[PIAI_NAMESPACE] : {};
2971
+ const providers = isMapLike(piAi.providers) ? piAi.providers : {};
2972
+ const section = isMapLike(providers[providerId]) ? providers[providerId] : void 0;
2973
+ if (section === void 0) {
2974
+ throw new Error(`provider ${providerId} \u4E0D\u5B58\u5728\uFF0C\u8BF7\u5148\u7528 /provider add \u521B\u5EFA`);
2975
+ }
2976
+ const models = rawModels(section.models);
2977
+ if (models.some((model) => model.id === input.id)) {
2978
+ throw new Error(`\u6A21\u578B ${input.id} \u5DF2\u5B58\u5728\u4E8E ${providerId}`);
2979
+ }
2980
+ await this.writeNamespace(PIAI_NAMESPACE, {
2981
+ ...piAi,
2982
+ providers: {
2983
+ ...providers,
2984
+ [providerId]: { ...section, models: [...models, modelRecord(input)] }
2985
+ }
2986
+ });
2987
+ }
2988
+ async removePiAiModel(providerId, modelId) {
2989
+ const settings = await this.readSettings();
2990
+ const piAi = isMapLike(settings[PIAI_NAMESPACE]) ? settings[PIAI_NAMESPACE] : {};
2991
+ const providers = isMapLike(piAi.providers) ? piAi.providers : {};
2992
+ const section = isMapLike(providers[providerId]) ? providers[providerId] : void 0;
2993
+ if (section === void 0) {
2994
+ throw new Error(`provider ${providerId} \u4E0D\u5B58\u5728`);
2995
+ }
2996
+ const models = rawModels(section.models);
2997
+ const next = models.filter((model) => model.id !== modelId);
2998
+ if (next.length === models.length) return false;
2999
+ await this.writeNamespace(PIAI_NAMESPACE, {
3000
+ ...piAi,
3001
+ providers: {
3002
+ ...providers,
3003
+ [providerId]: { ...section, models: next }
3004
+ }
3005
+ });
3006
+ return true;
3007
+ }
3008
+ async setCredential(ref, value) {
3009
+ if (!CREDENTIAL_REF_PATTERN.test(ref)) {
3010
+ throw new Error(`\u975E\u6CD5\u51ED\u636E\u5F15\u7528\u540D\uFF1A${ref}\uFF08\u5E94\u4E3A POSIX \u73AF\u5883\u53D8\u91CF\u540D\uFF0C\u5982 OPENAI_API_KEY\uFF09`);
3011
+ }
3012
+ if (value.length === 0) throw new Error("\u51ED\u636E\u503C\u4E0D\u80FD\u4E3A\u7A7A");
3013
+ await mkdir5(dirname3(this.credentialsFile), { recursive: true, mode: 448 });
3014
+ await withFileLock(this.credentialsFile, async () => {
3015
+ const text = await readOptional(this.credentialsFile);
3016
+ const root = parseYamlMap(text, this.credentialsFile);
3017
+ const document = text === void 0 || text.trim().length === 0 ? new Document() : parseDocument(text);
3018
+ if (document.errors.length > 0) {
3019
+ throw new Error(`invalid dsh credentials at ${this.credentialsFile}`);
3020
+ }
3021
+ patchNode(document, [ref], root[ref], value);
3022
+ await writeFileAtomic(this.credentialsFile, document.toString(), { mode: 384 });
3023
+ });
3024
+ }
3025
+ async removeCredential(ref) {
3026
+ let removed = false;
3027
+ await withFileLock(this.credentialsFile, async () => {
3028
+ const text = await readOptional(this.credentialsFile);
3029
+ if (text === void 0 || text.trim().length === 0) return;
3030
+ const document = parseDocument(text);
3031
+ if (document.errors.length > 0) {
3032
+ throw new Error(`invalid dsh credentials at ${this.credentialsFile}`);
3033
+ }
3034
+ if (document.has(ref)) {
3035
+ document.deleteIn([ref]);
3036
+ removed = true;
3037
+ await writeFileAtomic(this.credentialsFile, document.toString(), { mode: 384 });
3038
+ }
3039
+ });
3040
+ return removed;
3041
+ }
3042
+ async writeNamespace(ns, section) {
3043
+ await mkdir5(dirname3(this.settingsFile), { recursive: true });
3044
+ await withFileLock(this.settingsFile, async () => {
3045
+ const text = await readOptional(this.settingsFile);
3046
+ const root = parseYamlMap(text, this.settingsFile);
3047
+ if (text === void 0 || text.trim().length === 0) {
3048
+ await writeFileAtomic(this.settingsFile, new Document({ [ns]: section }).toString(), {});
3049
+ return;
3050
+ }
3051
+ const document = parseDocument(text);
3052
+ if (document.errors.length > 0) {
3053
+ throw new Error(`invalid dsh settings at ${this.settingsFile}`);
3054
+ }
3055
+ patchNode(document, [ns], root[ns], section);
3056
+ await writeFileAtomic(this.settingsFile, document.toString(), {});
3057
+ });
3058
+ }
3059
+ async deleteNamespace(ns) {
3060
+ await withFileLock(this.settingsFile, async () => {
3061
+ const text = await readOptional(this.settingsFile);
3062
+ if (text === void 0 || text.trim().length === 0) return;
3063
+ const document = parseDocument(text);
3064
+ if (document.errors.length > 0) {
3065
+ throw new Error(`invalid dsh settings at ${this.settingsFile}`);
3066
+ }
3067
+ if (document.has(ns)) {
3068
+ document.deleteIn([ns]);
3069
+ await writeFileAtomic(this.settingsFile, document.toString(), {});
3070
+ }
3071
+ });
3072
+ }
3073
+ };
3074
+
3075
+ // src/commands/models.ts
3076
+ function parseArgs(input) {
3077
+ const tokens = input.trim().split(/\s+/).filter(Boolean);
3078
+ const positionals = [];
3079
+ const flags = {};
3080
+ const repeated = {};
3081
+ for (let index = 0; index < tokens.length; index += 1) {
3082
+ const token = tokens[index] ?? "";
3083
+ if (!token.startsWith("--")) {
3084
+ positionals.push(token);
3085
+ continue;
3086
+ }
3087
+ const name = token.slice(2);
3088
+ const next = tokens[index + 1];
3089
+ let value;
3090
+ if (next !== void 0 && !next.startsWith("--")) {
3091
+ value = next;
3092
+ index += 1;
3093
+ }
3094
+ flags[name] = value;
3095
+ if (value !== void 0) {
3096
+ const list = repeated[name] ?? [];
3097
+ list.push(value);
3098
+ repeated[name] = list;
3099
+ }
3100
+ }
3101
+ return { positionals, flags, repeated };
3102
+ }
3103
+ async function reply(ctx, markdown2) {
3104
+ await ctx.channel.sendMarkdown(ctx.chatId, markdown2, {
3105
+ replyTo: ctx.messageId
3106
+ });
3107
+ }
3108
+ function requireAdmin(ctx) {
3109
+ if (!ctx.accessManager.isAdmin(ctx.senderId)) {
3110
+ void reply(ctx, "\u4EC5\u7BA1\u7406\u5458\u53EF\u6267\u884C\u8BE5\u64CD\u4F5C\u3002");
3111
+ return false;
3112
+ }
3113
+ return true;
3114
+ }
3115
+ function formatModel(model) {
3116
+ return model.name ? `\`${model.id}\` (${model.name})` : `\`${model.id}\``;
3117
+ }
3118
+ function credentialLabel(provider) {
3119
+ if (provider.credentialReady) return "\u51ED\u636E\u5C31\u7EEA";
3120
+ return provider.credentialRef === void 0 ? "\u672A\u914D\u7F6E\u51ED\u636E" : "\u51ED\u636E\u7F3A\u5931";
3121
+ }
3122
+ function formatProviders(providers) {
3123
+ const lines = providers.map((provider) => {
3124
+ const state = provider.configured ? "\u5DF2\u914D\u7F6E" : "\u672A\u914D\u7F6E";
3125
+ const models = provider.models.length > 0 ? provider.models.map((model) => model.id).join(", ") : "(\u65E0)";
3126
+ return `- **${provider.id}** (${provider.displayName}) \u2014 ${state} \xB7 ${credentialLabel(provider)}
3127
+ models: ${models}`;
3128
+ });
3129
+ return lines.join("\n");
3130
+ }
3131
+ async function findModelOwner(ctx, id) {
3132
+ const providers = await ctx.dshConfig.listProviders();
3133
+ return providers.find((provider) => provider.models.some((model) => model.id === id));
3134
+ }
3135
+ function deepseekModelInput(args) {
3136
+ return {
3137
+ id: args.id,
3138
+ name: args.flags.name,
3139
+ contextWindow: void 0,
3140
+ maxTokens: void 0
3141
+ };
3142
+ }
3143
+ async function handleModel(args, ctx) {
3144
+ const { positionals, flags } = parseArgs(args);
3145
+ const [sub, ...rest] = positionals;
3146
+ if (!sub) {
3147
+ const active = ctx.models.get(ctx.scope) ?? ctx.defaultModel;
3148
+ const dshDefault = await ctx.dshConfig.defaultModel();
3149
+ const providers = await ctx.dshConfig.listProviders();
3150
+ const modelLines = providers.flatMap(
3151
+ (provider) => provider.models.map((model) => `- ${formatModel(model)} \u2190 ${provider.id}`)
3152
+ );
3153
+ await reply(
3154
+ ctx,
3155
+ [
3156
+ `**\u5F53\u524D\u4F1A\u8BDD\u6A21\u578B**\uFF1A\`${active}\``,
3157
+ `**dsh \u9ED8\u8BA4\u6A21\u578B**\uFF08agent-default-model\uFF09\uFF1A\`${dshDefault ?? "(\u672A\u8BBE\u7F6E)"}\``,
3158
+ `**bot \u56DE\u9000\u9ED8\u8BA4**\uFF08profile / DSH_LARK_MODEL\uFF09\uFF1A\`${ctx.defaultModel}\``,
3159
+ "",
3160
+ "**\u53EF\u7528\u6A21\u578B**\uFF08dsh \u5DF2\u914D\u7F6E\uFF09\uFF1A",
3161
+ ...modelLines.length > 0 ? modelLines : ["\uFF08\u6682\u65E0\uFF09"],
3162
+ "",
3163
+ "\u7528\u6CD5\uFF1A`/model use <id>`\u3001`/model default <id>`\u3001`/model reset`\u3001`/model add|remove <provider> <modelId> [--name <name>]`"
3164
+ ].join("\n")
3165
+ );
3166
+ return;
3167
+ }
3168
+ if (sub === "use") {
3169
+ const id = rest.join(" ").trim();
3170
+ if (!id) {
3171
+ await reply(ctx, "\u7528\u6CD5\uFF1A`/model use <modelId>`");
3172
+ return;
3173
+ }
3174
+ const provider = await findModelOwner(ctx, id);
3175
+ if (!provider) {
3176
+ await reply(ctx, `\u672A\u627E\u5230\u6A21\u578B \`${id}\`\uFF0C\u53EF\u7528 /model \u67E5\u770B\u5217\u8868\u3002`);
3177
+ return;
3178
+ }
3179
+ ctx.models.set(ctx.scope, id);
3180
+ await reply(ctx, `\u5DF2\u70ED\u5207\u6362\u5F53\u524D\u4F1A\u8BDD\u6A21\u578B\uFF1A\`${id}\`\uFF08\u4E0B\u4E00\u8F6E\u6D88\u606F\u751F\u6548\uFF0C\u65E0\u9700\u91CD\u542F bot\uFF09\u3002`);
3181
+ return;
3182
+ }
3183
+ if (sub === "reset") {
3184
+ const cleared = ctx.models.clear(ctx.scope);
3185
+ await reply(ctx, cleared ? "\u5DF2\u6E05\u9664\u5F53\u524D\u4F1A\u8BDD\u6A21\u578B\u8986\u76D6\uFF0C\u6062\u590D bot \u9ED8\u8BA4\u6A21\u578B\u3002" : "\u5F53\u524D\u4F1A\u8BDD\u6CA1\u6709\u6A21\u578B\u8986\u76D6\u3002");
3186
+ return;
3187
+ }
3188
+ if (sub === "default") {
3189
+ const id = rest.join(" ").trim();
3190
+ if (!id) {
3191
+ await reply(ctx, "\u7528\u6CD5\uFF1A`/model default <modelId>`");
3192
+ return;
3193
+ }
3194
+ const provider = await findModelOwner(ctx, id);
3195
+ if (!provider) {
3196
+ await reply(ctx, `\u672A\u627E\u5230\u6A21\u578B \`${id}\`\uFF0C\u53EF\u7528 /model \u67E5\u770B\u5217\u8868\u3002`);
3197
+ return;
3198
+ }
3199
+ if (!requireAdmin(ctx)) return;
3200
+ await ctx.dshConfig.setDefaultModel(id);
3201
+ await reply(ctx, `\u5DF2\u5199\u5165 dsh \u9ED8\u8BA4\u6A21\u578B\uFF08agent-default-model\uFF09\uFF1A\`${id}\`\uFF0C\u65B0\u4F1A\u8BDD\u751F\u6548\u3002`);
3202
+ return;
3203
+ }
3204
+ if (sub === "add" || sub === "remove") {
3205
+ if (!requireAdmin(ctx)) return;
3206
+ const providerId = rest[0];
3207
+ const id = rest[1];
3208
+ if (!providerId || !id) {
3209
+ await reply(ctx, `\u7528\u6CD5\uFF1A\`/model ${sub} <provider> <modelId> [--name <name>]\``);
3210
+ return;
3211
+ }
3212
+ if (sub === "add") {
3213
+ if (providerId === DEEPSEEK_PROVIDER) {
3214
+ await ctx.dshConfig.addDeepseekModel(deepseekModelInput({ id, flags }));
3215
+ await reply(ctx, `\u5DF2\u6DFB\u52A0\u6A21\u578B\uFF1A\`${providerId}\` \u2192 \`${id}\`\uFF08settings.yaml \u5DF2\u66F4\u65B0\uFF0C\u4E0B\u4E00\u8BF7\u6C42\u751F\u6548\uFF09\u3002`);
3216
+ } else {
3217
+ await ctx.dshConfig.addPiAiModel(providerId, deepseekModelInput({ id, flags }));
3218
+ await reply(ctx, `\u5DF2\u6DFB\u52A0\u6A21\u578B\uFF1A\`${providerId}\` \u2192 \`${id}\`\uFF08settings.yaml \u5DF2\u66F4\u65B0\uFF0C\u4E0B\u4E00\u8BF7\u6C42\u751F\u6548\uFF09\u3002`);
3219
+ }
3220
+ } else if (providerId === DEEPSEEK_PROVIDER) {
3221
+ const removed = await ctx.dshConfig.removeDeepseekModel(id);
3222
+ await reply(ctx, removed ? `\u5DF2\u5220\u9664\u6A21\u578B\uFF1A\`${providerId}\` \u2192 \`${id}\`\u3002` : `\u672A\u627E\u5230\u6A21\u578B\uFF1A\`${id}\`\u3002`);
3223
+ } else {
3224
+ const removed = await ctx.dshConfig.removePiAiModel(providerId, id);
3225
+ await reply(ctx, removed ? `\u5DF2\u5220\u9664\u6A21\u578B\uFF1A\`${providerId}\` \u2192 \`${id}\`\u3002` : `\u672A\u627E\u5230\u6A21\u578B\uFF1A\`${id}\`\u3002`);
3226
+ }
3227
+ return;
3228
+ }
3229
+ await reply(ctx, "\u672A\u77E5 `/model` \u5B50\u547D\u4EE4\uFF0C\u8BF7\u4F7F\u7528 use / default / reset / add / remove\u3002");
3230
+ }
3231
+ async function handleProviders(_args, ctx) {
3232
+ const providers = await ctx.dshConfig.listProviders();
3233
+ const dshDefault = await ctx.dshConfig.defaultModel();
3234
+ await reply(
3235
+ ctx,
3236
+ [
3237
+ "**dsh \u5DF2\u914D\u7F6E providers**",
3238
+ "",
3239
+ ...formatProviders(providers).split("\n"),
3240
+ "",
3241
+ `dsh \u9ED8\u8BA4\u6A21\u578B\uFF1A\`${dshDefault ?? "(\u672A\u8BBE\u7F6E)"}\``,
3242
+ "",
3243
+ "\u7BA1\u7406\uFF1A`/provider add|update|remove`\u3001`/model add|remove`\u3001`/key set|remove`\uFF08\u9700\u7BA1\u7406\u5458\uFF09"
3244
+ ].join("\n")
3245
+ );
3246
+ }
3247
+ async function handleProvider(args, ctx) {
3248
+ const { positionals, flags, repeated } = parseArgs(args);
3249
+ const [sub, id] = positionals;
3250
+ if ((sub === "add" || sub === "update") && id) {
3251
+ if (!requireAdmin(ctx)) return;
3252
+ const deepseekFlags = ["base-url", "api-key-env", "api-key"];
3253
+ const piAiFlags = [
3254
+ "display-name",
3255
+ "api-key-env",
3256
+ "api",
3257
+ "base-url",
3258
+ "model"
3259
+ ];
3260
+ const provided = /* @__PURE__ */ new Set([...Object.keys(flags), ...Object.keys(repeated)]);
3261
+ if (id === DEEPSEEK_PROVIDER) {
3262
+ const unsupported2 = [...provided].filter((name) => !deepseekFlags.includes(name));
3263
+ if (unsupported2.length > 0) {
3264
+ await reply(ctx, `\`deepseek-official\` \u4EC5\u652F\u6301 ${deepseekFlags.map((name) => `--${name}`).join(" / ")}\uFF0C\u4E0D\u652F\u6301\u7684\u53C2\u6570\uFF1A${unsupported2.map((name) => `--${name}`).join("\u3001")}`);
3265
+ return;
3266
+ }
3267
+ if (provided.size === 0) {
3268
+ await reply(ctx, "\u7528\u6CD5\uFF1A`/provider add|update deepseek-official --base-url <url> [--api-key-env <ref>] [--api-key <key>]`");
3269
+ return;
3270
+ }
3271
+ await ctx.dshConfig.upsertDeepseekProvider({
3272
+ ...flags["base-url"] ? { baseURL: flags["base-url"] } : {},
3273
+ ...flags["api-key-env"] ? { apiKeyEnv: flags["api-key-env"] } : {},
3274
+ ...flags["api-key"] ? { apiKey: flags["api-key"] } : {}
3275
+ });
3276
+ await reply(
3277
+ ctx,
3278
+ `\u5DF2${sub === "add" ? "\u6DFB\u52A0/\u66F4\u65B0" : "\u66F4\u65B0"} provider\uFF1A\`${id}\`\u3002\u51ED\u636E${flags["api-key"] ? "\u5DF2\u5199\u5165 .credentials.yaml\uFF08\u503C\u5DF2\u9690\u85CF\uFF09" : "\u4E0D\u53D8"}\uFF1B\u5176\u4ED6\u5B57\u6BB5\u6309\u53C2\u6570\u66F4\u65B0\u3002`
3279
+ );
3280
+ return;
3281
+ }
3282
+ const unsupported = [...provided].filter((name) => !piAiFlags.includes(name));
3283
+ if (unsupported.length > 0) {
3284
+ await reply(ctx, `\u4E0D\u652F\u6301\u7684\u53C2\u6570\uFF1A${unsupported.map((name) => `--${name}`).join("\u3001")}\u3002\u53EF\u7528\uFF1A${piAiFlags.map((name) => `--${name}`).join(" / ")}`);
3285
+ return;
3286
+ }
3287
+ if (provided.size === 0) {
3288
+ await reply(ctx, "\u7528\u6CD5\uFF1A`/provider add|update <id> --api <protocol> --base-url <url> --model <modelId> [--display-name <name>] [--api-key-env <ref>]`");
3289
+ return;
3290
+ }
3291
+ const models = (repeated.model ?? []).map((modelId) => ({
3292
+ id: modelId,
3293
+ name: void 0,
3294
+ contextWindow: void 0,
3295
+ maxTokens: void 0
3296
+ }));
3297
+ try {
3298
+ await ctx.dshConfig.upsertPiAiProvider({
3299
+ id,
3300
+ ...flags["display-name"] ? { displayName: flags["display-name"] } : {},
3301
+ ...flags["api-key-env"] ? { apiKeyEnv: flags["api-key-env"] } : {},
3302
+ ...flags.api ? { api: flags.api } : {},
3303
+ ...flags["base-url"] ? { baseURL: flags["base-url"] } : {},
3304
+ ...models.length > 0 ? { models } : {}
3305
+ });
3306
+ const protocolNote = flags.api ? `\u534F\u8BAE \`${flags.api}\`` : "\u534F\u8BAE\u4E0D\u53D8";
3307
+ const modelNote = models.length > 0 ? `\uFF0Cmodels \u5DF2${sub === "add" ? "\u5199\u5165" : "\u66FF\u6362\u4E3A"} ${models.length} \u4E2A` : "";
3308
+ await reply(
3309
+ ctx,
3310
+ [
3311
+ `\u5DF2${sub === "add" ? "\u6DFB\u52A0" : "\u66F4\u65B0"} provider\uFF1A\`${id}\`\uFF08${protocolNote}${modelNote}\uFF09\u3002`,
3312
+ "",
3313
+ "\u51ED\u636E\u8BF7\u7528 `/key set <\u5F15\u7528\u540D> <\u503C>` \u5199\u5165\uFF08\u6216\u914D\u7F6E\u5BF9\u5E94\u73AF\u5883\u53D8\u91CF\uFF09\uFF0C\u5BC6\u94A5\u4E0D\u4F1A\u663E\u793A\u5728\u804A\u5929\u4E2D\u3002",
3314
+ `\u534F\u8BAE\u53EF\u9009\uFF1A${SUPPORTED_PI_AI_PROTOCOLS.join(" / ")}`
3315
+ ].join("\n")
3316
+ );
3317
+ } catch (error) {
3318
+ await reply(ctx, `\u64CD\u4F5C\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}`);
3319
+ }
3320
+ return;
3321
+ }
3322
+ if (sub === "remove" && id) {
3323
+ if (!requireAdmin(ctx)) return;
3324
+ if (id === DEEPSEEK_PROVIDER) {
3325
+ await ctx.dshConfig.removeDeepseekProvider();
3326
+ await reply(
3327
+ ctx,
3328
+ `\u5DF2\u5220\u9664 provider\uFF1A\`${id}\`\uFF08\u542B settings \u4E2D llm-deepseek \u6BB5\u53CA\u5BF9\u5E94\u51ED\u636E\uFF09\u3002\u6B64\u64CD\u4F5C\u4F1A\u79FB\u9664 dsh \u5B98\u65B9 DeepSeek \u914D\u7F6E\uFF0C\u8BF7\u8C28\u614E\u3002`
3329
+ );
3330
+ return;
3331
+ }
3332
+ const removed = await ctx.dshConfig.removePiAiProvider(id);
3333
+ await reply(ctx, removed ? `\u5DF2\u5220\u9664 provider\uFF1A\`${id}\`\uFF08settings.yaml \u5DF2\u66F4\u65B0\uFF09\u3002` : `\u672A\u627E\u5230 provider\uFF1A\`${id}\`\u3002`);
3334
+ return;
3335
+ }
3336
+ await reply(
3337
+ ctx,
3338
+ [
3339
+ "\u7528\u6CD5\uFF1A",
3340
+ "- `/provider add <id> --api <protocol> --base-url <url> --model <modelId> [--model <modelId> ...] [--display-name <name>] [--api-key-env <ref>]`",
3341
+ "- `/provider update <id> [--api <protocol>] [--base-url <url>] [--model <modelId> ...] [--display-name <name>] [--api-key-env <ref>]`",
3342
+ "- `/provider remove <id>`",
3343
+ "",
3344
+ `\`deepseek-official\` \u4EC5\u652F\u6301 --base-url / --api-key-env / --api-key\uFF1B\u5176\u4ED6 id \u8D70 llm-pi-ai \u81EA\u5B9A\u4E49 provider\uFF08\u534F\u8BAE\u53EF\u9009\uFF1A${SUPPORTED_PI_AI_PROTOCOLS.join(" / ")}\uFF09\u3002`
3345
+ ].join("\n")
3346
+ );
3347
+ }
3348
+ async function handleKey(args, ctx) {
3349
+ const { positionals } = parseArgs(args);
3350
+ const [sub, ...rest] = positionals;
3351
+ if (!sub || sub === "list") {
3352
+ const refs = await ctx.dshConfig.listCredentialRefs();
3353
+ await reply(
3354
+ ctx,
3355
+ [
3356
+ "**dsh \u51ED\u636E**\uFF08\u4EC5\u663E\u793A\u5F15\u7528\u540D\uFF0C\u4E0D\u663E\u793A\u503C\uFF09\uFF1A",
3357
+ ...refs.length > 0 ? refs.map((ref) => `- \`${ref}\``) : ["\uFF08.credentials.yaml \u6682\u65E0\u6761\u76EE\uFF09"],
3358
+ "",
3359
+ "\u63D0\u793A\uFF1A\u73AF\u5883\u53D8\u91CF\u4E2D\u7684\u51ED\u636E\u4E0D\u4F1A\u663E\u793A\u5728\u8FD9\u91CC\uFF1Bprovider \u7684 apiKeyEnv \u53EF\u5F15\u7528\u4EFB\u610F\u73AF\u5883\u53D8\u91CF\u3002"
3360
+ ].join("\n")
3361
+ );
3362
+ return;
3363
+ }
3364
+ if (!requireAdmin(ctx)) return;
3365
+ if (sub === "set") {
3366
+ const ref = rest[0];
3367
+ const value = rest.slice(1).join(" ").trim();
3368
+ if (!ref || !value) {
3369
+ await reply(ctx, "\u7528\u6CD5\uFF1A`/key set <\u5F15\u7528\u540D> <\u503C>`");
3370
+ return;
3371
+ }
3372
+ try {
3373
+ await ctx.dshConfig.setCredential(ref, value);
3374
+ } catch (error) {
3375
+ await reply(ctx, `\u5199\u5165\u5931\u8D25\uFF1A${error instanceof Error ? error.message : String(error)}`);
3376
+ return;
3377
+ }
3378
+ await reply(
3379
+ ctx,
3380
+ `\u5DF2\u5199\u5165\u51ED\u636E \`${ref}\` \u5230 \`~/.dsh/.credentials.yaml\`\uFF080600\uFF0C\u503C\u5DF2\u9690\u85CF\uFF09\u3002\u5EFA\u8BAE\u5728\u79C1\u804A\u4E2D\u4F7F\u7528\uFF1B\u7FA4\u804A\u91CC\u7C98\u8D34\u7684\u5BC6\u94A5\u4F1A\u5BF9\u7FA4\u6210\u5458\u53EF\u89C1\u3002`
3381
+ );
3382
+ return;
3383
+ }
3384
+ if (sub === "remove") {
3385
+ const ref = rest[0];
3386
+ if (!ref) {
3387
+ await reply(ctx, "\u7528\u6CD5\uFF1A`/key remove <\u5F15\u7528\u540D>`");
3388
+ return;
3389
+ }
3390
+ const removed = await ctx.dshConfig.removeCredential(ref);
3391
+ await reply(ctx, removed ? `\u5DF2\u5220\u9664\u51ED\u636E \`${ref}\`\u3002` : `\u672A\u627E\u5230\u51ED\u636E \`${ref}\`\u3002`);
3392
+ return;
3393
+ }
3394
+ await reply(ctx, "\u7528\u6CD5\uFF1A`/key set <\u5F15\u7528\u540D> <\u503C>`\u3001`/key remove <\u5F15\u7528\u540D>`\u3001`/key list`");
3395
+ }
3396
+
2639
3397
  // src/commands/index.ts
2640
3398
  var HELP = [
2641
3399
  "**dsh-lark-bot \u547D\u4EE4**",
@@ -2648,11 +3406,18 @@ var HELP = [
2648
3406
  "- `/stop` \u2014 \u7EC8\u6B62\u5F53\u524D\u4EFB\u52A1",
2649
3407
  "- `/timeout [N|off|default]` \u2014 \u67E5\u770B\u6216\u8BBE\u7F6E\u5F53\u524D\u4F1A\u8BDD\u8FD0\u884C\u8D85\u65F6",
2650
3408
  "- `/density [compact|standard|detailed]` \u2014 \u67E5\u770B\u6216\u8BBE\u7F6E\u5361\u7247\u5BC6\u5EA6",
3409
+ "- `/model` \u2014 \u67E5\u770B\u5F53\u524D\u6A21\u578B\u4E0E dsh \u53EF\u7528\u6A21\u578B",
3410
+ "- `/model use <id>` \u2014 \u70ED\u5207\u6362\u5F53\u524D\u4F1A\u8BDD\u6A21\u578B\uFF08\u4E0B\u4E00\u8F6E\u751F\u6548\uFF09",
3411
+ "- `/model default <id>` \u2014 \u5199\u5165 dsh \u9ED8\u8BA4\u6A21\u578B agent-default-model\uFF08\u7BA1\u7406\u5458\uFF09",
3412
+ "- `/model add|remove <provider> <modelId>` \u2014 \u7BA1\u7406 provider \u7684\u6A21\u578B\uFF08\u7BA1\u7406\u5458\uFF09",
3413
+ "- `/providers` \u2014 \u67E5\u770B dsh providers / \u6A21\u578B / \u51ED\u636E\u72B6\u6001",
3414
+ "- `/provider add|update|remove <id>` \u2014 \u7BA1\u7406 provider\uFF08\u7BA1\u7406\u5458\uFF1Bdeepseek-official \u4E0E\u81EA\u5B9A\u4E49 pi-ai\uFF09",
3415
+ "- `/key set|remove|list <\u5F15\u7528\u540D>` \u2014 \u7BA1\u7406 dsh \u51ED\u636E\uFF08set/remove \u9700\u7BA1\u7406\u5458\uFF09",
2651
3416
  "- `/ask <\u95EE\u9898>` \u2014 \u53D1\u9001\u7ED3\u6784\u5316\u95EE\u7B54\u5361\uFF08\u56DE\u7B54\u5C06\u8BB0\u5165\u4F1A\u8BDD\uFF09",
2652
3417
  "- `/invite user|admin|group <id>` \u2014 \u7BA1\u7406\u8BBF\u95EE\u767D\u540D\u5355",
2653
3418
  "- `/help` \u2014 \u663E\u793A\u672C\u5E2E\u52A9"
2654
3419
  ].join("\n");
2655
- async function reply(ctx, markdown2) {
3420
+ async function reply2(ctx, markdown2) {
2656
3421
  await ctx.channel.sendMarkdown(ctx.chatId, markdown2, {
2657
3422
  replyTo: ctx.messageId
2658
3423
  });
@@ -2660,19 +3425,19 @@ async function reply(ctx, markdown2) {
2660
3425
  async function handleNew(_args, ctx) {
2661
3426
  const wasRunning = await ctx.activeRuns.interrupt(ctx.scope);
2662
3427
  ctx.sessions.clear(ctx.scope);
2663
- await reply(ctx, wasRunning ? "\u5DF2\u4E2D\u65AD\u5F53\u524D\u4EFB\u52A1\u5E76\u5F00\u59CB\u65B0\u4F1A\u8BDD\u3002" : "\u5DF2\u5F00\u59CB\u65B0\u4F1A\u8BDD\u3002");
3428
+ await reply2(ctx, wasRunning ? "\u5DF2\u4E2D\u65AD\u5F53\u524D\u4EFB\u52A1\u5E76\u5F00\u59CB\u65B0\u4F1A\u8BDD\u3002" : "\u5DF2\u5F00\u59CB\u65B0\u4F1A\u8BDD\u3002");
2664
3429
  }
2665
3430
  async function handleCd(args, ctx) {
2666
3431
  const path = args.trim();
2667
3432
  if (!path) {
2668
- await reply(ctx, "\u7528\u6CD5\uFF1A`/cd <path>`");
3433
+ await reply2(ctx, "\u7528\u6CD5\uFF1A`/cd <path>`");
2669
3434
  return;
2670
3435
  }
2671
3436
  const cwd = resolve4(path);
2672
3437
  await ctx.activeRuns.interrupt(ctx.scope);
2673
3438
  ctx.workspaces.setCwd(ctx.scope, cwd);
2674
3439
  ctx.sessions.clear(ctx.scope);
2675
- await reply(ctx, `\u5DF2\u5207\u6362\u5DE5\u4F5C\u76EE\u5F55\uFF1A\`${cwd}\`\uFF0C\u4F1A\u8BDD\u5DF2\u91CD\u7F6E\u3002`);
3440
+ await reply2(ctx, `\u5DF2\u5207\u6362\u5DE5\u4F5C\u76EE\u5F55\uFF1A\`${cwd}\`\uFF0C\u4F1A\u8BDD\u5DF2\u91CD\u7F6E\u3002`);
2676
3441
  }
2677
3442
  async function handleWs(args, ctx) {
2678
3443
  const [sub, ...rest] = args.trim().split(/\s+/);
@@ -2688,7 +3453,7 @@ async function handleWs(args, ctx) {
2688
3453
  const lines = Object.entries(named).map(
2689
3454
  ([key, value]) => `- **${key}** \u2192 \`${value}\`${value === current ? " \u2190 \u5F53\u524D" : ""}`
2690
3455
  );
2691
- await reply(
3456
+ await reply2(
2692
3457
  ctx,
2693
3458
  [
2694
3459
  `\u5F53\u524D cwd\uFF1A\`${current}\``,
@@ -2700,48 +3465,48 @@ async function handleWs(args, ctx) {
2700
3465
  }
2701
3466
  if (sub === "save") {
2702
3467
  if (!name) {
2703
- await reply(ctx, "\u7528\u6CD5\uFF1A`/ws save <name>`");
3468
+ await reply2(ctx, "\u7528\u6CD5\uFF1A`/ws save <name>`");
2704
3469
  return;
2705
3470
  }
2706
3471
  const current = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
2707
3472
  ctx.workspaces.saveNamed(name, current);
2708
- await reply(ctx, `\u5DF2\u4FDD\u5B58\u5DE5\u4F5C\u7A7A\u95F4\uFF1A**${name}** \u2192 \`${current}\``);
3473
+ await reply2(ctx, `\u5DF2\u4FDD\u5B58\u5DE5\u4F5C\u7A7A\u95F4\uFF1A**${name}** \u2192 \`${current}\``);
2709
3474
  return;
2710
3475
  }
2711
3476
  if (sub === "use") {
2712
3477
  if (!name) {
2713
- await reply(ctx, "\u7528\u6CD5\uFF1A`/ws use <name>`");
3478
+ await reply2(ctx, "\u7528\u6CD5\uFF1A`/ws use <name>`");
2714
3479
  return;
2715
3480
  }
2716
3481
  const cwd = ctx.workspaces.getNamed(name);
2717
3482
  if (!cwd) {
2718
- await reply(ctx, `\u672A\u627E\u5230\u5DE5\u4F5C\u7A7A\u95F4\uFF1A**${name}**`);
3483
+ await reply2(ctx, `\u672A\u627E\u5230\u5DE5\u4F5C\u7A7A\u95F4\uFF1A**${name}**`);
2719
3484
  return;
2720
3485
  }
2721
3486
  await ctx.activeRuns.interrupt(ctx.scope);
2722
3487
  ctx.workspaces.setCwd(ctx.scope, cwd);
2723
3488
  ctx.workspaces.touchNamed(name);
2724
3489
  ctx.sessions.clear(ctx.scope);
2725
- await reply(ctx, `\u5DF2\u5207\u6362\u5230\u5DE5\u4F5C\u7A7A\u95F4\uFF1A**${name}** \u2192 \`${cwd}\``);
3490
+ await reply2(ctx, `\u5DF2\u5207\u6362\u5230\u5DE5\u4F5C\u7A7A\u95F4\uFF1A**${name}** \u2192 \`${cwd}\``);
2726
3491
  return;
2727
3492
  }
2728
3493
  if (sub === "remove") {
2729
3494
  if (!name) {
2730
- await reply(ctx, "\u7528\u6CD5\uFF1A`/ws remove <name>`");
3495
+ await reply2(ctx, "\u7528\u6CD5\uFF1A`/ws remove <name>`");
2731
3496
  return;
2732
3497
  }
2733
3498
  const removed = ctx.workspaces.removeNamed(name);
2734
- await reply(ctx, removed ? `\u5DF2\u5220\u9664\u5DE5\u4F5C\u7A7A\u95F4\uFF1A**${name}**` : `\u672A\u627E\u5230\u5DE5\u4F5C\u7A7A\u95F4\uFF1A**${name}**`);
3499
+ await reply2(ctx, removed ? `\u5DF2\u5220\u9664\u5DE5\u4F5C\u7A7A\u95F4\uFF1A**${name}**` : `\u672A\u627E\u5230\u5DE5\u4F5C\u7A7A\u95F4\uFF1A**${name}**`);
2735
3500
  return;
2736
3501
  }
2737
- await reply(ctx, "\u672A\u77E5 `/ws` \u5B50\u547D\u4EE4\uFF0C\u8BF7\u4F7F\u7528 list / save / use / remove\u3002");
3502
+ await reply2(ctx, "\u672A\u77E5 `/ws` \u5B50\u547D\u4EE4\uFF0C\u8BF7\u4F7F\u7528 list / save / use / remove\u3002");
2738
3503
  }
2739
3504
  async function handleStatus(_args, ctx) {
2740
3505
  const cwd = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
2741
3506
  const session = ctx.sessions.getRaw(ctx.scope)?.sessionId ?? "(\u65E0)";
2742
3507
  const running = Boolean(ctx.activeRuns.get(ctx.scope));
2743
3508
  const scopeLabel = ctx.chatMode === "topic" ? `${ctx.scope}\uFF08\u8BDD\u9898\u72EC\u7ACB session\uFF09` : ctx.scope;
2744
- await reply(
3509
+ await reply2(
2745
3510
  ctx,
2746
3511
  [
2747
3512
  `\u{1F9ED} **scope**: \`${scopeLabel}\``,
@@ -2755,25 +3520,25 @@ async function handleResume(_args, ctx) {
2755
3520
  const cwd = ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace;
2756
3521
  const history = ctx.sessions.historyFor(ctx.scope, cwd);
2757
3522
  if (history.length === 0) {
2758
- await reply(ctx, "\u5F53\u524D\u4F1A\u8BDD\u6CA1\u6709\u5386\u53F2\u4E0A\u4E0B\u6587\u3002");
3523
+ await reply2(ctx, "\u5F53\u524D\u4F1A\u8BDD\u6CA1\u6709\u5386\u53F2\u4E0A\u4E0B\u6587\u3002");
2759
3524
  return;
2760
3525
  }
2761
3526
  const recent = history.slice(-6).map((message) => {
2762
3527
  const speaker = message.role === "user" ? "\u{1F464}" : "\u{1F916}";
2763
3528
  return `${speaker} ${message.content.slice(0, 300)}`;
2764
3529
  });
2765
- await reply(ctx, [`\u5F53\u524D scope\uFF1A\`${ctx.scope}\``, "", ...recent].join("\n"));
3530
+ await reply2(ctx, [`\u5F53\u524D scope\uFF1A\`${ctx.scope}\``, "", ...recent].join("\n"));
2766
3531
  }
2767
3532
  async function handleStop(_args, ctx) {
2768
3533
  const stopped = await ctx.activeRuns.interrupt(ctx.scope);
2769
- await reply(ctx, stopped ? "\u5DF2\u8BF7\u6C42\u7EC8\u6B62\u5F53\u524D\u4EFB\u52A1\u3002" : "\u5F53\u524D\u6CA1\u6709\u8FD0\u884C\u4E2D\u7684\u4EFB\u52A1\u3002");
3534
+ await reply2(ctx, stopped ? "\u5DF2\u8BF7\u6C42\u7EC8\u6B62\u5F53\u524D\u4EFB\u52A1\u3002" : "\u5F53\u524D\u6CA1\u6709\u8FD0\u884C\u4E2D\u7684\u4EFB\u52A1\u3002");
2770
3535
  }
2771
3536
  async function handleTimeout(args, ctx) {
2772
3537
  const input = args.trim();
2773
3538
  const effectiveMs = ctx.runPolicies.get(ctx.scope) ?? ctx.defaultRunTimeoutMs;
2774
3539
  if (!input) {
2775
3540
  const minutes2 = effectiveMs > 0 ? Math.round(effectiveMs / 6e4) : 0;
2776
- await reply(
3541
+ await reply2(
2777
3542
  ctx,
2778
3543
  minutes2 > 0 ? `\u5F53\u524D\u4F1A\u8BDD\u8FD0\u884C\u8D85\u65F6\uFF1A${minutes2} \u5206\u949F\u3002\u53EF\u7528 \`/timeout <N|off|default>\` \u8C03\u6574\u3002` : "\u5F53\u524D\u4F1A\u8BDD\u8FD0\u884C\u8D85\u65F6\uFF1A\u5173\u95ED\u3002"
2779
3544
  );
@@ -2781,27 +3546,27 @@ async function handleTimeout(args, ctx) {
2781
3546
  }
2782
3547
  if (input === "off") {
2783
3548
  ctx.runPolicies.set(ctx.scope, 0);
2784
- await reply(ctx, "\u5DF2\u5173\u95ED\u5F53\u524D\u4F1A\u8BDD\u8FD0\u884C\u8D85\u65F6\u3002");
3549
+ await reply2(ctx, "\u5DF2\u5173\u95ED\u5F53\u524D\u4F1A\u8BDD\u8FD0\u884C\u8D85\u65F6\u3002");
2785
3550
  return;
2786
3551
  }
2787
3552
  if (input === "default") {
2788
3553
  ctx.runPolicies.clear(ctx.scope);
2789
- await reply(ctx, "\u5DF2\u6062\u590D\u9ED8\u8BA4\u8FD0\u884C\u8D85\u65F6\u3002");
3554
+ await reply2(ctx, "\u5DF2\u6062\u590D\u9ED8\u8BA4\u8FD0\u884C\u8D85\u65F6\u3002");
2790
3555
  return;
2791
3556
  }
2792
3557
  const minutes = Number(input);
2793
3558
  if (!Number.isInteger(minutes) || minutes <= 0) {
2794
- await reply(ctx, "\u7528\u6CD5\uFF1A`/timeout <N|off|default>`\uFF0CN \u4E3A\u5927\u4E8E 0 \u7684\u5206\u949F\u6570\u3002");
3559
+ await reply2(ctx, "\u7528\u6CD5\uFF1A`/timeout <N|off|default>`\uFF0CN \u4E3A\u5927\u4E8E 0 \u7684\u5206\u949F\u6570\u3002");
2795
3560
  return;
2796
3561
  }
2797
3562
  ctx.runPolicies.set(ctx.scope, minutes * 6e4);
2798
- await reply(ctx, `\u5DF2\u8BBE\u7F6E\u5F53\u524D\u4F1A\u8BDD\u8FD0\u884C\u8D85\u65F6\uFF1A${minutes} \u5206\u949F\u3002`);
3563
+ await reply2(ctx, `\u5DF2\u8BBE\u7F6E\u5F53\u524D\u4F1A\u8BDD\u8FD0\u884C\u8D85\u65F6\uFF1A${minutes} \u5206\u949F\u3002`);
2799
3564
  }
2800
3565
  async function handleDensity(args, ctx) {
2801
3566
  const input = args.trim().toLowerCase();
2802
3567
  if (!input) {
2803
3568
  const current = ctx.densityStore?.get(ctx.scope) ?? "standard";
2804
- await reply(
3569
+ await reply2(
2805
3570
  ctx,
2806
3571
  `\u5F53\u524D\u5361\u7247\u5BC6\u5EA6\uFF1A**${current}**\u3002\u53EF\u7528 \`/density compact|standard|detailed\` \u8C03\u6574\u3002`
2807
3572
  );
@@ -2809,25 +3574,25 @@ async function handleDensity(args, ctx) {
2809
3574
  }
2810
3575
  if (input === "default") {
2811
3576
  ctx.densityStore?.clear(ctx.scope);
2812
- await reply(ctx, "\u5DF2\u6062\u590D\u9ED8\u8BA4\u5361\u7247\u5BC6\u5EA6\u3002");
3577
+ await reply2(ctx, "\u5DF2\u6062\u590D\u9ED8\u8BA4\u5361\u7247\u5BC6\u5EA6\u3002");
2813
3578
  return;
2814
3579
  }
2815
3580
  const density = parseCardDensity(input);
2816
3581
  if (!density) {
2817
- await reply(ctx, "\u7528\u6CD5\uFF1A`/density [compact|standard|detailed|default]`");
3582
+ await reply2(ctx, "\u7528\u6CD5\uFF1A`/density [compact|standard|detailed|default]`");
2818
3583
  return;
2819
3584
  }
2820
3585
  ctx.densityStore?.set(ctx.scope, density);
2821
- await reply(ctx, `\u5DF2\u8BBE\u7F6E\u5F53\u524D\u4F1A\u8BDD\u5361\u7247\u5BC6\u5EA6\uFF1A**${density}**\u3002`);
3586
+ await reply2(ctx, `\u5DF2\u8BBE\u7F6E\u5F53\u524D\u4F1A\u8BDD\u5361\u7247\u5BC6\u5EA6\uFF1A**${density}**\u3002`);
2822
3587
  }
2823
3588
  async function handleAsk(args, ctx) {
2824
3589
  const question = args.trim();
2825
3590
  if (!question) {
2826
- await reply(ctx, "\u7528\u6CD5\uFF1A`/ask <\u95EE\u9898>`");
3591
+ await reply2(ctx, "\u7528\u6CD5\uFF1A`/ask <\u95EE\u9898>`");
2827
3592
  return;
2828
3593
  }
2829
3594
  if (!ctx.questions) {
2830
- await reply(ctx, "\u95EE\u7B54\u5361\u672A\u542F\u7528\uFF08\u8BF7\u786E\u8BA4 questions \u5DF2\u63A5\u7EBF\uFF09\u3002");
3595
+ await reply2(ctx, "\u95EE\u7B54\u5361\u672A\u542F\u7528\uFF08\u8BF7\u786E\u8BA4 questions \u5DF2\u63A5\u7EBF\uFF09\u3002");
2831
3596
  return;
2832
3597
  }
2833
3598
  const answer = await questionHandlerFor({
@@ -2843,9 +3608,9 @@ async function handleAsk(args, ctx) {
2843
3608
  if (answer !== void 0) {
2844
3609
  const text = Array.isArray(answer) ? answer.join("\u3001") : answer;
2845
3610
  ctx.sessions.recordExchange(ctx.scope, ctx.workspaces.cwdFor(ctx.scope) ?? ctx.defaultWorkspace, [text], void 0);
2846
- await reply(ctx, `\u5DF2\u8BB0\u5F55\u4F60\u7684\u56DE\u7B54\uFF0C\u5E76\u5199\u5165\u4F1A\u8BDD\u4E0A\u4E0B\u6587\u3002`);
3611
+ await reply2(ctx, `\u5DF2\u8BB0\u5F55\u4F60\u7684\u56DE\u7B54\uFF0C\u5E76\u5199\u5165\u4F1A\u8BDD\u4E0A\u4E0B\u6587\u3002`);
2847
3612
  } else {
2848
- await reply(ctx, "\u672A\u6536\u5230\u56DE\u7B54\uFF08\u5361\u7247\u53EF\u80FD\u5DF2\u8D85\u65F6\u6216\u88AB\u5FFD\u7565\uFF09\u3002");
3613
+ await reply2(ctx, "\u672A\u6536\u5230\u56DE\u7B54\uFF08\u5361\u7247\u53EF\u80FD\u5DF2\u8D85\u65F6\u6216\u88AB\u5FFD\u7565\uFF09\u3002");
2849
3614
  }
2850
3615
  }
2851
3616
  async function handleInvite(args, ctx) {
@@ -2853,7 +3618,7 @@ async function handleInvite(args, ctx) {
2853
3618
  const id = rest.join(" ").trim();
2854
3619
  if (kind === "list") {
2855
3620
  const snapshot = ctx.accessManager.snapshot();
2856
- await reply(
3621
+ await reply2(
2857
3622
  ctx,
2858
3623
  [
2859
3624
  "**\u8BBF\u95EE\u767D\u540D\u5355**",
@@ -2865,7 +3630,7 @@ async function handleInvite(args, ctx) {
2865
3630
  return;
2866
3631
  }
2867
3632
  if (!kind || !id) {
2868
- await reply(
3633
+ await reply2(
2869
3634
  ctx,
2870
3635
  "\u7528\u6CD5\uFF1A`/invite user|admin|group <id>`\u3001`/invite list`\u3001`/invite remove user|group <id>`"
2871
3636
  );
@@ -2873,38 +3638,38 @@ async function handleInvite(args, ctx) {
2873
3638
  }
2874
3639
  if (kind === "user") {
2875
3640
  await ctx.accessManager.addUser(id);
2876
- await reply(ctx, `\u5DF2\u5141\u8BB8\u7528\u6237\uFF1A\`${id}\``);
3641
+ await reply2(ctx, `\u5DF2\u5141\u8BB8\u7528\u6237\uFF1A\`${id}\``);
2877
3642
  return;
2878
3643
  }
2879
3644
  if (kind === "admin") {
2880
3645
  await ctx.accessManager.addAdmin(id);
2881
- await reply(ctx, `\u5DF2\u8BBE\u4E3A\u7BA1\u7406\u5458\uFF1A\`${id}\``);
3646
+ await reply2(ctx, `\u5DF2\u8BBE\u4E3A\u7BA1\u7406\u5458\uFF1A\`${id}\``);
2882
3647
  return;
2883
3648
  }
2884
3649
  if (kind === "group") {
2885
3650
  await ctx.accessManager.addChat(id);
2886
- await reply(ctx, `\u5DF2\u5141\u8BB8\u7FA4\u804A\uFF1A\`${id}\``);
3651
+ await reply2(ctx, `\u5DF2\u5141\u8BB8\u7FA4\u804A\uFF1A\`${id}\``);
2887
3652
  return;
2888
3653
  }
2889
3654
  if (kind === "remove") {
2890
3655
  const [sub, target] = rest;
2891
3656
  if (sub === "user" && target) {
2892
3657
  await ctx.accessManager.removeUser(target);
2893
- await reply(ctx, `\u5DF2\u79FB\u9664\u7528\u6237\uFF1A\`${target}\``);
3658
+ await reply2(ctx, `\u5DF2\u79FB\u9664\u7528\u6237\uFF1A\`${target}\``);
2894
3659
  return;
2895
3660
  }
2896
3661
  if (sub === "group" && target) {
2897
3662
  await ctx.accessManager.removeChat(target);
2898
- await reply(ctx, `\u5DF2\u79FB\u9664\u7FA4\u804A\uFF1A\`${target}\``);
3663
+ await reply2(ctx, `\u5DF2\u79FB\u9664\u7FA4\u804A\uFF1A\`${target}\``);
2899
3664
  return;
2900
3665
  }
2901
- await reply(ctx, "\u7528\u6CD5\uFF1A`/invite remove user <id>` \u6216 `/invite remove group <chatId>`");
3666
+ await reply2(ctx, "\u7528\u6CD5\uFF1A`/invite remove user <id>` \u6216 `/invite remove group <chatId>`");
2902
3667
  return;
2903
3668
  }
2904
- await reply(ctx, "\u672A\u77E5 `/invite` \u7C7B\u578B\uFF0C\u8BF7\u4F7F\u7528 user / admin / group / list / remove\u3002");
3669
+ await reply2(ctx, "\u672A\u77E5 `/invite` \u7C7B\u578B\uFF0C\u8BF7\u4F7F\u7528 user / admin / group / list / remove\u3002");
2905
3670
  }
2906
3671
  async function handleHelp(_args, ctx) {
2907
- await reply(ctx, HELP);
3672
+ await reply2(ctx, HELP);
2908
3673
  }
2909
3674
  var handlers = {
2910
3675
  "/new": handleNew,
@@ -2916,6 +3681,10 @@ var handlers = {
2916
3681
  "/stop": handleStop,
2917
3682
  "/timeout": handleTimeout,
2918
3683
  "/density": handleDensity,
3684
+ "/model": handleModel,
3685
+ "/providers": handleProviders,
3686
+ "/provider": handleProvider,
3687
+ "/key": handleKey,
2919
3688
  "/ask": handleAsk,
2920
3689
  "/invite": handleInvite,
2921
3690
  "/help": handleHelp
@@ -3016,8 +3785,12 @@ async function startChannel(deps) {
3016
3785
  approvals: deps.approvals,
3017
3786
  questions: deps.questions,
3018
3787
  densityStore: deps.densityStore,
3788
+ models: deps.models,
3789
+ dshConfig: deps.dshConfig,
3019
3790
  channel: commandChannel,
3020
- defaultWorkspace: deps.defaultWorkspace
3791
+ defaultWorkspace: deps.defaultWorkspace,
3792
+ defaultModel: deps.defaultModel,
3793
+ senderId: msg.senderId
3021
3794
  };
3022
3795
  const handled = await tryHandleCommand(msg.content, context).catch((error) => {
3023
3796
  log.fail("channel-command", error, { scope });
@@ -3090,6 +3863,9 @@ var AccessManager = class {
3090
3863
  admins: [...profile.access.admins]
3091
3864
  };
3092
3865
  }
3866
+ isAdmin(openId) {
3867
+ return Boolean(openId && this.snapshot().admins.includes(openId));
3868
+ }
3093
3869
  async addUser(id) {
3094
3870
  const snapshot = this.snapshot();
3095
3871
  if (!snapshot.allowedUsers.includes(id)) snapshot.allowedUsers.push(id);
@@ -3180,8 +3956,8 @@ async function onboardPersonalAgent(deps = {}) {
3180
3956
  }
3181
3957
 
3182
3958
  // src/media/attachments.ts
3183
- import { mkdir as mkdir5, readFile as readFile2, stat as stat2 } from "fs/promises";
3184
- import { join as join7 } from "path";
3959
+ import { mkdir as mkdir6, readFile as readFile3, stat as stat2 } from "fs/promises";
3960
+ import { join as join8 } from "path";
3185
3961
  var MAX_TEXT_FILE_BYTES = 256e3;
3186
3962
  function assertSafeMediaName(mediaDir, destination) {
3187
3963
  if (!isPathWithin(mediaDir, destination)) {
@@ -3189,11 +3965,11 @@ function assertSafeMediaName(mediaDir, destination) {
3189
3965
  }
3190
3966
  }
3191
3967
  async function prepareAttachments(channel, message, mediaDir) {
3192
- await mkdir5(mediaDir, { recursive: true });
3968
+ await mkdir6(mediaDir, { recursive: true });
3193
3969
  const result = { imagePaths: [], textFileNotes: [] };
3194
3970
  for (const resource of message.resources) {
3195
3971
  if (!channel || resource.type !== "image" && resource.type !== "file") continue;
3196
- const destination = join7(mediaDir, `${message.messageId}-${resource.fileKey}`);
3972
+ const destination = join8(mediaDir, `${message.messageId}-${resource.fileKey}`);
3197
3973
  assertSafeMediaName(mediaDir, destination);
3198
3974
  await channel.downloadResourceToFile(
3199
3975
  message.messageId,
@@ -3210,7 +3986,7 @@ async function prepareAttachments(channel, message, mediaDir) {
3210
3986
  result.textFileNotes.push(`[attachment: ${resource.fileName ?? resource.fileKey}] ${destination}`);
3211
3987
  continue;
3212
3988
  }
3213
- const content = await readFile2(destination, "utf8");
3989
+ const content = await readFile3(destination, "utf8");
3214
3990
  const safeContent = truncateUtf8Safe(content, MAX_TEXT_FILE_BYTES);
3215
3991
  result.textFileNotes.push(
3216
3992
  `[attachment: ${resource.fileName ?? resource.fileKey}]
@@ -3221,7 +3997,7 @@ ${safeContent}`
3221
3997
  }
3222
3998
 
3223
3999
  // src/session/store.ts
3224
- import { readFile as readFile3 } from "fs/promises";
4000
+ import { readFile as readFile4 } from "fs/promises";
3225
4001
  var SessionStore = class {
3226
4002
  data = { chats: {} };
3227
4003
  saving = Promise.resolve();
@@ -3231,7 +4007,7 @@ var SessionStore = class {
3231
4007
  }
3232
4008
  async load() {
3233
4009
  try {
3234
- const raw = await readFile3(this.path, "utf8");
4010
+ const raw = await readFile4(this.path, "utf8");
3235
4011
  const parsed = JSON.parse(raw);
3236
4012
  this.data = {
3237
4013
  chats: Object.fromEntries(
@@ -3335,8 +4111,8 @@ var SessionStore = class {
3335
4111
  // src/workspace/git-worktree.ts
3336
4112
  import { execFile } from "child_process";
3337
4113
  import { randomBytes as randomBytes2 } from "crypto";
3338
- import { access, copyFile, mkdir as mkdir6 } from "fs/promises";
3339
- import { dirname as dirname3, join as join8 } from "path";
4114
+ import { access, copyFile, mkdir as mkdir7 } from "fs/promises";
4115
+ import { dirname as dirname4, join as join9 } from "path";
3340
4116
  import { promisify } from "util";
3341
4117
  var execFileAsync = promisify(execFile);
3342
4118
  async function defaultRunGit(args, cwd) {
@@ -3355,7 +4131,7 @@ async function exists(path) {
3355
4131
  }
3356
4132
  }
3357
4133
  async function defaultCopyRulesFile(source, target) {
3358
- await mkdir6(dirname3(target), { recursive: true });
4134
+ await mkdir7(dirname4(target), { recursive: true });
3359
4135
  await copyFile(source, target);
3360
4136
  }
3361
4137
  function slugify(scope) {
@@ -3375,7 +4151,7 @@ var GitWorktreeManager = class {
3375
4151
  const isGit = await this.isGitRepository(base);
3376
4152
  if (!isGit) return { cwd: base, created: false };
3377
4153
  const slug = slugify(scope);
3378
- const target = join8(this.options.worktreesRoot, slug);
4154
+ const target = join9(this.options.worktreesRoot, slug);
3379
4155
  if (!isPathWithin(this.options.worktreesRoot, target)) {
3380
4156
  throw new Error(`unsafe worktree target rejected: ${target}`);
3381
4157
  }
@@ -3384,7 +4160,7 @@ var GitWorktreeManager = class {
3384
4160
  return { cwd: target, created: false };
3385
4161
  }
3386
4162
  const branch = `dsh-lark/${slug}-${Date.now().toString(36)}-${randomBytes2(3).toString("hex")}`;
3387
- await mkdir6(this.options.worktreesRoot, { recursive: true });
4163
+ await mkdir7(this.options.worktreesRoot, { recursive: true });
3388
4164
  await this.runGit(["worktree", "add", "-b", branch, target, "HEAD"], base);
3389
4165
  await this.ensureProjectRules(base, target);
3390
4166
  return { cwd: target, created: true, branch };
@@ -3398,9 +4174,9 @@ var GitWorktreeManager = class {
3398
4174
  }
3399
4175
  }
3400
4176
  async ensureProjectRules(base, target) {
3401
- const destination = join8(target, "AGENTS.md");
4177
+ const destination = join9(target, "AGENTS.md");
3402
4178
  if (await exists(destination)) return;
3403
- const sourceCandidates = [join8(base, ".dsh-lark", "AGENTS.md"), join8(base, "AGENTS.md")];
4179
+ const sourceCandidates = [join9(base, ".dsh-lark", "AGENTS.md"), join9(base, "AGENTS.md")];
3404
4180
  for (const source of sourceCandidates) {
3405
4181
  if (await exists(source)) {
3406
4182
  await this.copyRulesFile(source, destination);
@@ -3411,7 +4187,7 @@ var GitWorktreeManager = class {
3411
4187
  };
3412
4188
 
3413
4189
  // src/workspace/store.ts
3414
- import { readFile as readFile4 } from "fs/promises";
4190
+ import { readFile as readFile5 } from "fs/promises";
3415
4191
  var WorkspaceStore = class {
3416
4192
  data = { chats: {}, named: {}, lastUsed: {} };
3417
4193
  saving = Promise.resolve();
@@ -3421,7 +4197,7 @@ var WorkspaceStore = class {
3421
4197
  }
3422
4198
  async load() {
3423
4199
  try {
3424
- const raw = await readFile4(this.path, "utf8");
4200
+ const raw = await readFile5(this.path, "utf8");
3425
4201
  const parsed = JSON.parse(raw);
3426
4202
  this.data = {
3427
4203
  chats: parsed.chats ?? {},
@@ -3569,7 +4345,7 @@ async function runBot(options) {
3569
4345
  return;
3570
4346
  }
3571
4347
  const defaultWorkspace = options.workspace ?? activeProfile.workspaces.default ?? env.workspace ?? paths.profilePath(profileName, "workspace");
3572
- await mkdir7(defaultWorkspace, { recursive: true });
4348
+ await mkdir8(defaultWorkspace, { recursive: true });
3573
4349
  const sessions = new SessionStore(paths.sessionsFile(profileName));
3574
4350
  const workspaces = new WorkspaceStore(paths.workspacesFile(profileName));
3575
4351
  const worktreeManager = new GitWorktreeManager({
@@ -3594,6 +4370,8 @@ async function runBot(options) {
3594
4370
  const approvals = new ApprovalRegistry();
3595
4371
  const questions = new QuestionRegistry();
3596
4372
  const densityStore = new DensityStore();
4373
+ const models = new ModelStore();
4374
+ const dshConfig = new DshProviderManager({ env: process.env });
3597
4375
  let streaming;
3598
4376
  let larkChannel;
3599
4377
  const pending = new PendingQueue(DEBOUNCE_MS, async (scope, batch) => {
@@ -3628,9 +4406,9 @@ async function runBot(options) {
3628
4406
  defaultWorkspace,
3629
4407
  replyTo: first.messageId,
3630
4408
  runTimeoutMs: activeProfile.preferences.runTimeoutMs ?? env.runTimeoutMs,
3631
- images: attachments.imagePaths
4409
+ images: attachments.imagePaths,
4410
+ model: models.get(scope) ?? activeProfile.preferences.model ?? await dshConfig.defaultModel().catch(() => void 0) ?? env.model
3632
4411
  };
3633
- if (activeProfile.preferences.model !== void 0) runInput.model = activeProfile.preferences.model;
3634
4412
  if (activeProfile.preferences.stopGraceMs !== void 0) {
3635
4413
  runInput.stopGraceMs = activeProfile.preferences.stopGraceMs;
3636
4414
  }
@@ -3651,7 +4429,10 @@ async function runBot(options) {
3651
4429
  approvals,
3652
4430
  questions,
3653
4431
  densityStore,
4432
+ models,
4433
+ dshConfig,
3654
4434
  defaultRunTimeoutMs: activeProfile.preferences.runTimeoutMs ?? env.runTimeoutMs,
4435
+ defaultModel: activeProfile.preferences.model ?? env.model,
3655
4436
  accessManager: new AccessManager(configStore, profileName),
3656
4437
  pending,
3657
4438
  defaultWorkspace,
@@ -3660,7 +4441,6 @@ async function runBot(options) {
3660
4441
  allowedUsers: activeProfile.access.allowedUsers,
3661
4442
  allowedChats: activeProfile.access.allowedChats
3662
4443
  };
3663
- if (activeProfile.preferences.model !== void 0) channelInput.model = activeProfile.preferences.model;
3664
4444
  if (activeProfile.preferences.stopGraceMs !== void 0) {
3665
4445
  channelInput.stopGraceMs = activeProfile.preferences.stopGraceMs;
3666
4446
  }
@@ -3696,12 +4476,12 @@ function waitForShutdown() {
3696
4476
  }
3697
4477
 
3698
4478
  // src/service/manager.ts
3699
- import { mkdir as mkdir12, readFile as readFile7 } from "fs/promises";
3700
- import { dirname as dirname6 } from "path";
4479
+ import { mkdir as mkdir13, readFile as readFile8 } from "fs/promises";
4480
+ import { dirname as dirname7 } from "path";
3701
4481
 
3702
4482
  // src/service/command.ts
3703
4483
  import { execFile as execFile2 } from "child_process";
3704
- import { basename, dirname as dirname4, join as join9 } from "path";
4484
+ import { basename, dirname as dirname5, join as join10 } from "path";
3705
4485
  import { fileURLToPath } from "url";
3706
4486
  var runCommand = (command, args, options) => new Promise((resolve5) => {
3707
4487
  execFile2(
@@ -3726,7 +4506,7 @@ function resolveCliJsPath(metaUrl = import.meta.url) {
3726
4506
  const current = fileURLToPath(metaUrl);
3727
4507
  const name = basename(current);
3728
4508
  if (name === "cli.js" || name === "cli.mjs" || name === "cli.cjs") return current;
3729
- return join9(dirname4(current), "..", "dist", "cli.js");
4509
+ return join10(dirname5(current), "..", "dist", "cli.js");
3730
4510
  }
3731
4511
  function slugify2(value) {
3732
4512
  const slug = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
@@ -3740,7 +4520,7 @@ function launchdLabelFor(profile) {
3740
4520
  }
3741
4521
 
3742
4522
  // src/service/env-snapshot.ts
3743
- import { readFile as readFile5 } from "fs/promises";
4523
+ import { readFile as readFile6 } from "fs/promises";
3744
4524
  var EXTRA_KEYS = ["PATH", "HOME", "DEEPSEEK_API_KEY", "DSH_HOME"];
3745
4525
  function snapshotServiceEnv(source = process.env) {
3746
4526
  const env = {};
@@ -3790,7 +4570,7 @@ async function writeServiceEnv(file, env) {
3790
4570
  }
3791
4571
  async function readServiceEnv(file) {
3792
4572
  try {
3793
- return parseEnvFile(await readFile5(file, "utf8"));
4573
+ return parseEnvFile(await readFile6(file, "utf8"));
3794
4574
  } catch (error) {
3795
4575
  if (error.code === "ENOENT") return {};
3796
4576
  throw error;
@@ -3798,11 +4578,11 @@ async function readServiceEnv(file) {
3798
4578
  }
3799
4579
 
3800
4580
  // src/service/linux-systemd.ts
3801
- import { mkdir as mkdir8, stat as stat3 } from "fs/promises";
3802
- import { homedir as homedir5 } from "os";
3803
- import { join as join10 } from "path";
4581
+ import { mkdir as mkdir9, stat as stat3 } from "fs/promises";
4582
+ import { homedir as homedir6 } from "os";
4583
+ import { join as join11 } from "path";
3804
4584
  function defaultSystemdUnitDir() {
3805
- return join10(homedir5(), ".config", "systemd", "user");
4585
+ return join11(homedir6(), ".config", "systemd", "user");
3806
4586
  }
3807
4587
  function quoteSystemdArg(arg) {
3808
4588
  if (!/[\s"\\]/.test(arg)) return arg;
@@ -3864,7 +4644,7 @@ var SystemdServiceController = class {
3864
4644
  this.unitDir = options.userUnitDir ?? defaultSystemdUnitDir();
3865
4645
  }
3866
4646
  unitPath(spec) {
3867
- return join10(this.unitDir, `${spec.serviceName}.service`);
4647
+ return join11(this.unitDir, `${spec.serviceName}.service`);
3868
4648
  }
3869
4649
  unitName(spec) {
3870
4650
  return `${spec.serviceName}.service`;
@@ -3874,7 +4654,7 @@ var SystemdServiceController = class {
3874
4654
  return result.code === 0;
3875
4655
  }
3876
4656
  async installAndStart(spec) {
3877
- await mkdir8(this.unitDir, { recursive: true });
4657
+ await mkdir9(this.unitDir, { recursive: true });
3878
4658
  await writeFileAtomic(this.unitPath(spec), buildSystemdUnit(spec));
3879
4659
  await this.run("systemctl", ["--user", "daemon-reload"]);
3880
4660
  const name = this.unitName(spec);
@@ -3939,11 +4719,11 @@ var SystemdServiceController = class {
3939
4719
  };
3940
4720
 
3941
4721
  // src/service/macos-launchd.ts
3942
- import { mkdir as mkdir9, rm, stat as stat4 } from "fs/promises";
3943
- import { homedir as homedir6 } from "os";
3944
- import { join as join11 } from "path";
4722
+ import { mkdir as mkdir10, rm as rm2, stat as stat4 } from "fs/promises";
4723
+ import { homedir as homedir7 } from "os";
4724
+ import { join as join12 } from "path";
3945
4725
  function defaultLaunchAgentDir() {
3946
- return join11(homedir6(), "Library", "LaunchAgents");
4726
+ return join12(homedir7(), "Library", "LaunchAgents");
3947
4727
  }
3948
4728
  function xmlEscape(value) {
3949
4729
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
@@ -4008,13 +4788,13 @@ var LaunchdServiceController = class {
4008
4788
  return `gui/${this.uid}`;
4009
4789
  }
4010
4790
  plistPath(spec) {
4011
- return join11(this.agentDir, `${spec.label}.plist`);
4791
+ return join12(this.agentDir, `${spec.label}.plist`);
4012
4792
  }
4013
4793
  serviceRef(spec) {
4014
4794
  return `${this.domain()}/${spec.label}`;
4015
4795
  }
4016
4796
  async installAndStart(spec) {
4017
- await mkdir9(this.agentDir, { recursive: true });
4797
+ await mkdir10(this.agentDir, { recursive: true });
4018
4798
  await writeFileAtomic(this.plistPath(spec), buildLaunchdPlist(spec));
4019
4799
  const bootstrap = await this.run("launchctl", ["bootstrap", this.domain(), this.plistPath(spec)]);
4020
4800
  if (bootstrap.code !== 0) {
@@ -4023,7 +4803,7 @@ var LaunchdServiceController = class {
4023
4803
  }
4024
4804
  async stop(spec) {
4025
4805
  await this.run("launchctl", ["bootout", this.serviceRef(spec)]);
4026
- await rm(this.plistPath(spec), { force: true });
4806
+ await rm2(this.plistPath(spec), { force: true });
4027
4807
  }
4028
4808
  async restart(spec) {
4029
4809
  const kickstart = await this.run("launchctl", ["kickstart", "-k", this.serviceRef(spec)]);
@@ -4064,11 +4844,11 @@ var LaunchdServiceController = class {
4064
4844
 
4065
4845
  // src/service/portable.ts
4066
4846
  import { spawn as spawn5 } from "child_process";
4067
- import { mkdir as mkdir10, readFile as readFile6, rm as rm2, stat as stat5, writeFile as writeFile4 } from "fs/promises";
4068
- import { homedir as homedir7 } from "os";
4069
- import { join as join12 } from "path";
4847
+ import { mkdir as mkdir11, readFile as readFile7, rm as rm3, stat as stat5, writeFile as writeFile5 } from "fs/promises";
4848
+ import { homedir as homedir8 } from "os";
4849
+ import { join as join13 } from "path";
4070
4850
  function defaultAutostartDir() {
4071
- return join12(homedir7(), ".config", "autostart");
4851
+ return join13(homedir8(), ".config", "autostart");
4072
4852
  }
4073
4853
  function quoteDesktopArg(arg) {
4074
4854
  const escaped = arg.replace(/%/g, "%%").replace(/\\/g, "\\\\").replace(/"/g, '\\"');
@@ -4089,7 +4869,7 @@ function buildDesktopEntry(spec) {
4089
4869
  ].join("\n");
4090
4870
  }
4091
4871
  function supervisorStatusFile(serviceDir, serviceName) {
4092
- return join12(serviceDir, `supervisor-${serviceName}.json`);
4872
+ return join13(serviceDir, `supervisor-${serviceName}.json`);
4093
4873
  }
4094
4874
  function isProcessAlive(pid) {
4095
4875
  try {
@@ -4101,7 +4881,7 @@ function isProcessAlive(pid) {
4101
4881
  }
4102
4882
  async function readSupervisorStatus(file) {
4103
4883
  try {
4104
- return JSON.parse(await readFile6(file, "utf8"));
4884
+ return JSON.parse(await readFile7(file, "utf8"));
4105
4885
  } catch {
4106
4886
  return void 0;
4107
4887
  }
@@ -4126,7 +4906,7 @@ var PortableServiceController = class {
4126
4906
  this.spawnDetached = options.spawnDetached ?? defaultSpawnDetached;
4127
4907
  }
4128
4908
  desktopPath(spec) {
4129
- return join12(this.autostartDir, `${spec.serviceName}.desktop`);
4909
+ return join13(this.autostartDir, `${spec.serviceName}.desktop`);
4130
4910
  }
4131
4911
  statusFile(spec) {
4132
4912
  return supervisorStatusFile(this.serviceDir, spec.serviceName);
@@ -4150,15 +4930,15 @@ var PortableServiceController = class {
4150
4930
  }
4151
4931
  async installAndStart(spec) {
4152
4932
  await this.sendStopSignal(spec);
4153
- await mkdir10(this.autostartDir, { recursive: true });
4154
- await writeFile4(this.desktopPath(spec), buildDesktopEntry(spec), { encoding: "utf8", mode: 420 });
4933
+ await mkdir11(this.autostartDir, { recursive: true });
4934
+ await writeFile5(this.desktopPath(spec), buildDesktopEntry(spec), { encoding: "utf8", mode: 420 });
4155
4935
  if (!this.serviceDir) throw new Error("serviceDir is required for the portable supervisor");
4156
- await mkdir10(this.serviceDir, { recursive: true });
4936
+ await mkdir11(this.serviceDir, { recursive: true });
4157
4937
  this.spawnDetached(spec.nodePath, [spec.cliJsPath, "supervise", "--profile", spec.profile]);
4158
4938
  }
4159
4939
  async stop(spec) {
4160
4940
  await this.sendStopSignal(spec);
4161
- await rm2(this.desktopPath(spec), { force: true });
4941
+ await rm3(this.desktopPath(spec), { force: true });
4162
4942
  }
4163
4943
  async restart(spec) {
4164
4944
  await this.stop(spec);
@@ -4187,8 +4967,8 @@ var PortableServiceController = class {
4187
4967
  };
4188
4968
 
4189
4969
  // src/service/windows-task.ts
4190
- import { mkdir as mkdir11, writeFile as writeFile5 } from "fs/promises";
4191
- import { dirname as dirname5, join as join13 } from "path";
4970
+ import { mkdir as mkdir12, writeFile as writeFile6 } from "fs/promises";
4971
+ import { dirname as dirname6, join as join14 } from "path";
4192
4972
  function psQuote(value) {
4193
4973
  return `'${value.replace(/'/g, "''")}'`;
4194
4974
  }
@@ -4199,7 +4979,7 @@ function buildScheduledTaskScript(action, spec) {
4199
4979
  `$cli = ${psQuote(spec.cliJsPath)}`,
4200
4980
  `$log = ${psQuote(spec.logFile)}`,
4201
4981
  `$profile = ${psQuote(spec.profile)}`,
4202
- `$workDir = ${psQuote(dirname5(spec.cliJsPath))}`
4982
+ `$workDir = ${psQuote(dirname6(spec.cliJsPath))}`
4203
4983
  ].join("\n");
4204
4984
  const install = [
4205
4985
  `$inner = '"' + $node + '" "' + $cli + '" run --profile "' + $profile + '" >> "' + $log + '" 2>&1'`,
@@ -4280,12 +5060,12 @@ var WindowsTaskServiceController = class {
4280
5060
  this.scriptDir = options.scriptDir ?? "";
4281
5061
  }
4282
5062
  scriptPath(spec, action) {
4283
- return join13(this.scriptDir, `${action}-${spec.serviceName}.ps1`);
5063
+ return join14(this.scriptDir, `${action}-${spec.serviceName}.ps1`);
4284
5064
  }
4285
5065
  async runScript(spec, action) {
4286
5066
  const script = this.scriptPath(spec, action);
4287
- if (this.scriptDir) await mkdir11(this.scriptDir, { recursive: true });
4288
- await writeFile5(script, buildScheduledTaskScript(action, spec), { encoding: "utf8" });
5067
+ if (this.scriptDir) await mkdir12(this.scriptDir, { recursive: true });
5068
+ await writeFile6(script, buildScheduledTaskScript(action, spec), { encoding: "utf8" });
4289
5069
  const result = await this.run("powershell.exe", [
4290
5070
  "-NoProfile",
4291
5071
  "-NonInteractive",
@@ -4392,14 +5172,14 @@ var ServiceManager = class {
4392
5172
  }
4393
5173
  async readMetadata() {
4394
5174
  try {
4395
- return JSON.parse(await readFile7(this.paths.serviceMetadataFile, "utf8"));
5175
+ return JSON.parse(await readFile8(this.paths.serviceMetadataFile, "utf8"));
4396
5176
  } catch {
4397
5177
  return void 0;
4398
5178
  }
4399
5179
  }
4400
5180
  async prepareFiles(spec) {
4401
- await mkdir12(dirname6(spec.logFile), { recursive: true });
4402
- await mkdir12(dirname6(spec.envFile), { recursive: true });
5181
+ await mkdir13(dirname7(spec.logFile), { recursive: true });
5182
+ await mkdir13(dirname7(spec.envFile), { recursive: true });
4403
5183
  await writeServiceEnv(spec.envFile, spec.env);
4404
5184
  }
4405
5185
  async start() {
@@ -4530,7 +5310,7 @@ async function runServiceCommand(action, options, deps = {}) {
4530
5310
  // src/cli/commands/supervise.ts
4531
5311
  import { spawn as spawn6 } from "child_process";
4532
5312
  import { closeSync, mkdirSync, openSync } from "fs";
4533
- import { dirname as dirname7 } from "path";
5313
+ import { dirname as dirname8 } from "path";
4534
5314
  var BASE_BACKOFF_MS = 5e3;
4535
5315
  var MAX_BACKOFF_MS = 3e4;
4536
5316
  function computeBackoffMs(restarts) {
@@ -4543,7 +5323,7 @@ function waitForExit(child) {
4543
5323
  child.once("error", done);
4544
5324
  });
4545
5325
  }
4546
- function sleep(ms) {
5326
+ function sleep2(ms) {
4547
5327
  return new Promise((resolvePromise) => setTimeout(resolvePromise, ms));
4548
5328
  }
4549
5329
  async function runSupervise(options) {
@@ -4556,8 +5336,8 @@ async function runSupervise(options) {
4556
5336
  const serviceEnv = await readServiceEnv(paths.serviceEnvFile);
4557
5337
  const childEnv = { ...process.env, ...serviceEnv };
4558
5338
  const cliJsPath = resolveCliJsPath();
4559
- mkdirSync(dirname7(statusFile), { recursive: true });
4560
- mkdirSync(dirname7(logFile), { recursive: true });
5339
+ mkdirSync(dirname8(statusFile), { recursive: true });
5340
+ mkdirSync(dirname8(logFile), { recursive: true });
4561
5341
  const logFd = openSync(logFile, "a");
4562
5342
  const writeStatus = async (state, childPid, restarts2) => {
4563
5343
  const status = {
@@ -4595,7 +5375,7 @@ async function runSupervise(options) {
4595
5375
  restarts += 1;
4596
5376
  await writeStatus("restarting", void 0, restarts);
4597
5377
  const backoff = computeBackoffMs(restarts);
4598
- await sleep(backoff);
5378
+ await sleep2(backoff);
4599
5379
  }
4600
5380
  } finally {
4601
5381
  await writeStatus("stopped", void 0, restarts);
@@ -4653,8 +5433,20 @@ function buildProgram() {
4653
5433
  async function main(argv = process.argv) {
4654
5434
  await buildProgram().parseAsync([...argv]);
4655
5435
  }
5436
+ function isDirectInvocation(entry = process.argv[1], metaUrl = import.meta.url) {
5437
+ if (!entry) return false;
5438
+ try {
5439
+ return realpathSync2(entry) === fileURLToPath2(metaUrl);
5440
+ } catch {
5441
+ return false;
5442
+ }
5443
+ }
5444
+ if (isDirectInvocation()) {
5445
+ await main();
5446
+ }
4656
5447
  export {
4657
5448
  buildProgram,
5449
+ isDirectInvocation,
4658
5450
  main
4659
5451
  };
4660
5452
  //# sourceMappingURL=cli.js.map