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/README.md +24 -0
- package/dist/cli.d.ts +7 -1
- package/dist/cli.js +901 -109
- package/dist/cli.js.map +1 -1
- package/package.json +3 -2
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:
|
|
1525
|
+
const { rm: rm4 } = await import("fs/promises");
|
|
1525
1526
|
try {
|
|
1526
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
3591
|
+
await reply2(ctx, "\u7528\u6CD5\uFF1A`/ask <\u95EE\u9898>`");
|
|
2827
3592
|
return;
|
|
2828
3593
|
}
|
|
2829
3594
|
if (!ctx.questions) {
|
|
2830
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
3663
|
+
await reply2(ctx, `\u5DF2\u79FB\u9664\u7FA4\u804A\uFF1A\`${target}\``);
|
|
2899
3664
|
return;
|
|
2900
3665
|
}
|
|
2901
|
-
await
|
|
3666
|
+
await reply2(ctx, "\u7528\u6CD5\uFF1A`/invite remove user <id>` \u6216 `/invite remove group <chatId>`");
|
|
2902
3667
|
return;
|
|
2903
3668
|
}
|
|
2904
|
-
await
|
|
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
|
|
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
|
|
3184
|
-
import { join as
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
3339
|
-
import { dirname as
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
4177
|
+
const destination = join9(target, "AGENTS.md");
|
|
3402
4178
|
if (await exists(destination)) return;
|
|
3403
|
-
const sourceCandidates = [
|
|
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
|
|
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
|
|
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
|
|
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
|
|
3700
|
-
import { dirname as
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
3802
|
-
import { homedir as
|
|
3803
|
-
import { join as
|
|
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
|
|
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
|
|
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
|
|
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
|
|
3943
|
-
import { homedir as
|
|
3944
|
-
import { join as
|
|
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
|
|
4726
|
+
return join12(homedir7(), "Library", "LaunchAgents");
|
|
3947
4727
|
}
|
|
3948
4728
|
function xmlEscape(value) {
|
|
3949
4729
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
@@ -4008,13 +4788,13 @@ var LaunchdServiceController = class {
|
|
|
4008
4788
|
return `gui/${this.uid}`;
|
|
4009
4789
|
}
|
|
4010
4790
|
plistPath(spec) {
|
|
4011
|
-
return
|
|
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
|
|
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
|
|
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
|
|
4068
|
-
import { homedir as
|
|
4069
|
-
import { join as
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
4154
|
-
await
|
|
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
|
|
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
|
|
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
|
|
4191
|
-
import { dirname as
|
|
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(
|
|
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
|
|
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
|
|
4288
|
-
await
|
|
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
|
|
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
|
|
4402
|
-
await
|
|
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
|
|
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
|
|
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(
|
|
4560
|
-
mkdirSync(
|
|
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
|
|
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
|