engine7 7.1.95 → 7.1.105

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.
Files changed (4) hide show
  1. package/dist/cli.mjs +1784 -1347
  2. package/dist/engine-startup.mjs +1679 -1092
  3. package/dist/main.mjs +1679 -1092
  4. package/package.json +3 -2
package/dist/cli.mjs CHANGED
@@ -413,14 +413,14 @@ var init_live = __esm({
413
413
  }
414
414
  /** 把改动写回 config 文件对应段(找不到文件路径则只内存生效) */
415
415
  async persistToFile(dotPath, val) {
416
- const fs32 = await import("node:fs");
416
+ const fs33 = await import("node:fs");
417
417
  const cfgPath = this.current?._configFilePath;
418
- if (!cfgPath || !fs32.existsSync(cfgPath)) {
418
+ if (!cfgPath || !fs33.existsSync(cfgPath)) {
419
419
  console.warn(`[liveConfig] set: no config file path, in-memory only (${dotPath})`);
420
420
  return;
421
421
  }
422
422
  try {
423
- const raw = JSON.parse(fs32.readFileSync(cfgPath, "utf-8"));
423
+ const raw = JSON.parse(fs33.readFileSync(cfgPath, "utf-8"));
424
424
  const keys = dotPath.split(".");
425
425
  let o = raw;
426
426
  for (let i = 0; i < keys.length - 1; i++) {
@@ -428,7 +428,7 @@ var init_live = __esm({
428
428
  o = o[keys[i]];
429
429
  }
430
430
  o[keys[keys.length - 1]] = val;
431
- fs32.writeFileSync(cfgPath, JSON.stringify(raw, null, 2) + "\n", "utf-8");
431
+ fs33.writeFileSync(cfgPath, JSON.stringify(raw, null, 2) + "\n", "utf-8");
432
432
  console.log(`[liveConfig] persisted ${dotPath} = ${JSON.stringify(val)} to ${cfgPath}`);
433
433
  markSelfWrite();
434
434
  } catch (e) {
@@ -625,114 +625,536 @@ var init_clock = __esm({
625
625
  }
626
626
  });
627
627
 
628
- // src/config/secrets-env.ts
629
- import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
630
- import { basename as basename2, dirname as dirname2, join as join3 } from "node:path";
631
- function secretsHome() {
632
- return process.env.HOME || process.env.USERPROFILE || ".";
628
+ // src/cli-init-config.ts
629
+ import * as path2 from "node:path";
630
+ function isFreeTier(v) {
631
+ return v.freeTier === true || v.primaryProvider === "7yi";
633
632
  }
634
- function loadSecretsEnv(stateDir, configName) {
635
- const home = secretsHome();
636
- const candidates = [
637
- join3(home, ".engine7-secrets", `${configName}.env`),
638
- join3(stateDir, "configs", `.env.${configName}`),
639
- join3(stateDir, "configs", ".env")
640
- ];
641
- for (const p of candidates) {
642
- if (!existsSync3(p)) continue;
643
- for (const line of readFileSync3(p, "utf-8").split("\n")) {
644
- const t = line.trim();
645
- if (!t || t.startsWith("#")) continue;
646
- const eq = t.indexOf("=");
647
- if (eq <= 0) continue;
648
- const k = t.slice(0, eq).trim();
649
- const v = t.slice(eq + 1).trim();
650
- const cur = process.env[k];
651
- if (k && (cur === void 0 || cur === "")) process.env[k] = v;
652
- }
653
- return p;
654
- }
655
- return null;
633
+ function resolveRelayUrl(relayArg, env = process.env) {
634
+ return (relayArg || "").trim() || (env.E7_RELAY_URL || "").trim() || FREE_TIER_RELAY_URL;
656
635
  }
657
- function writeSecretsValue(configPath, envKey, value) {
658
- const configName = basename2(configPath, ".json");
659
- const dir = join3(secretsHome(), ".engine7-secrets");
660
- mkdirSync2(dir, { recursive: true });
661
- const file2 = join3(dir, `${configName}.env`);
662
- const lines = [];
663
- if (existsSync3(file2)) {
664
- for (const line of readFileSync3(file2, "utf8").split(/\r?\n/)) {
665
- if (new RegExp(`^\\s*${envKey}\\s*=`).test(line)) continue;
666
- lines.push(line);
667
- }
668
- }
669
- while (lines.length && lines[lines.length - 1] === "") lines.pop();
670
- lines.push(`${envKey}=${value}`);
671
- writeFileSync2(file2, lines.join("\n") + "\n", { mode: 384 });
672
- try {
673
- chmodSync(file2, 384);
674
- } catch {
675
- }
676
- return file2;
636
+ function modelIdOf(ref) {
637
+ const i = ref.indexOf("/");
638
+ return i >= 0 ? ref.slice(i + 1) : ref;
677
639
  }
678
- var init_secrets_env = __esm({
679
- "src/config/secrets-env.ts"() {
680
- "use strict";
681
- }
682
- });
683
-
684
- // src/config/display.ts
685
- function loadDisplayConfig(raw) {
686
- if (!raw) return { ...DEFAULT_DISPLAY };
687
- return {
688
- thinking: {
689
- ...DEFAULT_DISPLAY.thinking,
690
- ...raw.thinking || {}
640
+ function menuProviderOf(providerId) {
641
+ return PROVIDER_DEFS[providerId] ? providerId : "custom";
642
+ }
643
+ function resolveProviderTarget(input) {
644
+ const isCustom = !PROVIDER_DEFS[input.menuProvider];
645
+ const providerId = isCustom ? (input.customProviderId || "").trim() || "custom" : input.menuProvider;
646
+ const rawRef = (input.modelRef || "").trim();
647
+ const modelId = (rawRef.includes("/") ? modelIdOf(rawRef) : rawRef) || (input.modelId || "").trim();
648
+ return { providerId, modelId, modelRef: modelId ? `${providerId}/${modelId}` : "" };
649
+ }
650
+ function visionOf(ref, catalog) {
651
+ const hit = catalog?.find((m) => m.id === modelIdOf(ref));
652
+ if (hit && typeof hit.vision === "boolean") return hit.vision;
653
+ return VISION_CAPABLE[ref];
654
+ }
655
+ function builtinVisionModels(providerId) {
656
+ const def = PROVIDER_DEFS[providerId];
657
+ if (!def) return [];
658
+ return def.models.filter((m) => (m.input || []).includes("image")).map((m) => `${providerId}/${m.id}`);
659
+ }
660
+ function resolveVisionModel(providerId, primaryModel, catalog) {
661
+ if (visionOf(primaryModel, catalog) !== false) return primaryModel;
662
+ const candidates = catalog?.length ? catalog.filter((m) => m.vision === true).map((m) => `${providerId}/${m.id}`) : builtinVisionModels(providerId);
663
+ return candidates[0] || "";
664
+ }
665
+ function generateConfig(v) {
666
+ const workspace = path2.join(v.stateDir, "workspace").replace(/\\/g, "/");
667
+ const config = {
668
+ schemaVersion: SCHEMA_VERSION,
669
+ // 全引擎统一时区(design §3.1)。真实 init 走 getDefaultValues() 探测本机,
670
+ // 模板走 {{TIMEZONE}} 占位符。
671
+ timezone: v.timezone,
672
+ stateDir: v.stateDir.replace(/\\/g, "/"),
673
+ mediaDir: `${v.stateDir.replace(/\\/g, "/")}/media/inbound`,
674
+ models: {
675
+ providers: {}
691
676
  },
692
- toolUse: {
693
- ...DEFAULT_DISPLAY.toolUse,
694
- ...raw.toolUse || {}
677
+ // 0917:my_eyes(看图工具)读的是 tools.my_eyes.model,不是 tools.vision——
678
+ // 后者是模板里遗留的死键(全仓零读者),已删。visionModel 解析不出就不写这个块。
679
+ tools: {
680
+ ...v.tavilyKey ? { tavily: { apiKey: v.tavilyKey } } : {},
681
+ ...v.visionModel ? { my_eyes: { model: v.visionModel } } : {},
682
+ // 自拍(§4.3):免费档必须显式写 provider:'relay'。不写的话 getProvider()
683
+ // 会落回默认值 'fal',而免费装机没有 fal key——第一次自拍就失败。
684
+ // 非免费档只在用户真给了形象时才写这个块,且不替他决定 provider。
685
+ ...isFreeTier(v) || v.selfiePhotoRelPath || v.selfiePortraitDesc ? {
686
+ my_selfie: {
687
+ ...isFreeTier(v) ? { provider: "relay" } : {},
688
+ ...v.selfiePhotoRelPath ? { references: [{ name: "portrait", path: v.selfiePhotoRelPath }] } : {},
689
+ // 只在没有照片时才写描述——两个都给的话以照片为准,写进去会让人以为
690
+ // 描述也生效了(冷启读的就是这个键)
691
+ ...!v.selfiePhotoRelPath && v.selfiePortraitDesc ? { portraitDesc: v.selfiePortraitDesc } : {}
692
+ }
693
+ } : {}
695
694
  },
696
- toolResult: {
697
- ...DEFAULT_DISPLAY.toolResult,
698
- ...raw.toolResult || {}
695
+ session: { dmScope: "main", groupScope: "main" },
696
+ sandbox: { mode: "off" },
697
+ display: {
698
+ thinking: { enabled: false, emoji: "\u{1F4AD}", maxLen: 300 },
699
+ toolUse: { enabled: true, emoji: "\u{1F527}", displayMode: "raw", bashDisplayMode: "both" },
700
+ toolResult: { enabled: false },
701
+ reactions: { enabled: true, start: "\u{1F440}", done: "\u2705", error: "\u274C" },
702
+ preview: { enabled: true, agentName: v.agentName, color: "orange" },
703
+ typing: { enabled: true }
699
704
  },
700
- reactions: {
701
- ...DEFAULT_DISPLAY.reactions,
702
- ...raw.reactions || {}
705
+ agents: {
706
+ defaults: {
707
+ workspace,
708
+ name: v.agentName,
709
+ // 人设落 config 是为了**复跑能读回来**(loadExistingValues)。引擎运行时不读它
710
+ // ——SOUL.md 已经按人设渲染好了。不写的话复跑 init 会退回 'assistant',
711
+ // 而 execute() 用模板重写 SOUL.md,用户选的"情感陪伴"重装一次就变回通用助手。
712
+ persona: v.persona,
713
+ model: {
714
+ primary: v.primaryModel,
715
+ // 条件写入:vision 引用解析不出时省略键(loader 按 if (visionRef) 处理缺失)
716
+ ...v.visionModel ? { vision: v.visionModel } : {}
717
+ },
718
+ features: {
719
+ filesystem: true,
720
+ shell: true,
721
+ webSearch: true,
722
+ webFetch: true,
723
+ memory: true,
724
+ topics: true,
725
+ "topic-extract": true,
726
+ "topic-recall": true,
727
+ cron: true,
728
+ agent: true,
729
+ agentTeams: true,
730
+ processOutput: "minimal",
731
+ interrupt: "command",
732
+ debounceMs: 5e3,
733
+ // 免费档的装机目标是「能对话、能看图、能自拍」,这两个 flag 默认 false
734
+ // (src/config/features.ts)。显式打开,它们是声明这两件事的地方。
735
+ ...isFreeTier(v) ? { selfie: true, eyes: true } : {}
736
+ }
737
+ }
703
738
  },
704
- preview: {
705
- ...DEFAULT_DISPLAY.preview,
706
- ...raw.preview || {}
739
+ channels: {
740
+ discord: {
741
+ enabled: v.discordEnabled,
742
+ dmPolicy: "pairing",
743
+ groupPolicy: "open",
744
+ accounts: { default: { token: v.discordToken } },
745
+ replyToMode: "all"
746
+ },
747
+ feishu: {
748
+ enabled: v.feishuEnabled,
749
+ appId: v.feishuAppId,
750
+ appSecret: v.feishuAppSecret,
751
+ connectionMode: "websocket",
752
+ dmPolicy: "pairing",
753
+ groupPolicy: "open"
754
+ },
755
+ ...v.wechatEnabled && v.wechatToken ? {
756
+ wechat: {
757
+ enabled: true,
758
+ token: v.wechatToken,
759
+ accountId: v.wechatAccountId,
760
+ dmPolicy: "pairing",
761
+ group: { policy: "disabled" }
762
+ }
763
+ } : {},
764
+ // 0920 翀哥拍板:emotion 通道默认开启(含 liveSystemPrompt 加载 EMOTION.md)
765
+ // 即使没装 EMOTION.md,buildLightStablePrompt 也会硬编码"想 TA 就发"那段兜底
766
+ emotion: {
767
+ enabled: true,
768
+ liveSystemPrompt: {
769
+ files: ["SOUL.md", "EMOTION.md"]
770
+ }
771
+ }
707
772
  },
708
- typing: {
709
- ...DEFAULT_DISPLAY.typing,
710
- ...raw.typing || {}
773
+ api: { port: v.apiPort },
774
+ // 0909 npm 包默认开:inner-voice + proactive 是开箱即用的核心体验
775
+ // (不写 model → 都兜底主模型;proactive 领域/persona 可用 workspace/prompts/ 覆盖)
776
+ innerVoice: { enabled: true },
777
+ proactive: { enabled: true },
778
+ prompt: {
779
+ mode: "custom",
780
+ staticFiles: ["AGENTS.md", "USER.md", "MEMORY.md", "memory/distill-output.md"],
781
+ order: ["soul", "AGENTS.md", "prompts/contacts.md", "using-tools", "USER.md", "MEMORY.md", "memory/distill-output.md", "auto-memory-instructions"]
782
+ },
783
+ compaction: {
784
+ enabled: true,
785
+ bufferTokens: 23e3,
786
+ maxOutputTokens: 16384,
787
+ minReductionRatio: 0.3,
788
+ ruleBased: { enabled: true, essentialFields: [] },
789
+ memoryFlush: { enabled: true, forceFlushTranscriptBytes: "2.0mb" }
790
+ },
791
+ skills: {
792
+ enabled: true,
793
+ path: "skills"
711
794
  }
712
795
  };
713
- }
714
- var DEFAULT_DISPLAY;
715
- var init_display = __esm({
716
- "src/config/display.ts"() {
717
- "use strict";
718
- DEFAULT_DISPLAY = {
719
- thinking: { enabled: true, emoji: "\u{1F4AD}", maxLen: 300 },
720
- toolUse: { enabled: true, emoji: "\u{1F527}", displayMode: "raw", bashDisplayMode: "description", displayLimits: { write: 200 } },
721
- toolResult: { enabled: true, emoji: "\u{1F4E4}" },
722
- reactions: { enabled: true, start: "\u{1F440}", done: "\u2705", error: "\u274C" },
723
- preview: { enabled: true, agentName: "Agent" },
724
- typing: { enabled: true }
725
- };
726
- }
727
- });
728
-
729
- // src/config/features.ts
730
- function getFeature(key) {
731
- const v = liveConfig.get(`agents.defaults.features.${key}`);
732
- return v === void 0 ? FEATURE_DEFAULTS[key] : v;
733
- }
734
- var FEATURE_DEFAULTS;
735
- var init_features = __esm({
796
+ if (!PROVIDER_DEFS[v.primaryProvider]) {
797
+ const models = v.existingModels?.length ? [...v.existingModels] : [];
798
+ const customModelId = v.customModelId || models[0]?.id || modelIdOf(v.primaryModel);
799
+ if (!customModelId) {
800
+ throw new Error(
801
+ `\u81EA\u5B9A\u4E49 provider "${v.primaryProvider}" \u6CA1\u6709\u6A21\u578B id\u2014\u2014init \u751F\u6210\u4E0D\u51FA\u53EF\u7528\u7684 config\u3002\u8BF7\u5728\u5411\u5BFC\u91CC\u586B\u300C\u6A21\u578B ID\u300D\uFF0C\u6216\u8865\u4E0A configs/main7.json \u91CC models.providers.${v.primaryProvider}.models[0].id\u3002`
802
+ );
803
+ }
804
+ if (models.length && !models.some((m) => m?.id === customModelId)) {
805
+ models.unshift({ id: customModelId, name: customModelId, reasoning: false, input: CUSTOM_DEFAULT_INPUT, contextWindow: 2e5, maxTokens: 8192 });
806
+ }
807
+ config.models.providers[v.primaryProvider] = {
808
+ baseUrl: (v.customBaseUrl || "").trim(),
809
+ api: v.customApi,
810
+ apiKey: "env:" + v.primaryProvider.toUpperCase().replace(/[^A-Z0-9_]/g, "_") + "_API_KEY",
811
+ models: models.length ? models : [
812
+ { id: customModelId, name: customModelId, reasoning: false, input: CUSTOM_DEFAULT_INPUT, contextWindow: 2e5, maxTokens: 8192 }
813
+ ]
814
+ };
815
+ }
816
+ if (PROVIDER_DEFS[v.primaryProvider]) {
817
+ const def = { ...PROVIDER_DEFS[v.primaryProvider] };
818
+ if (v.primaryProvider === "zhipu" && v.zhipuPlan === "token") {
819
+ def.baseUrl = "https://open.bigmodel.cn/api/paas/v4";
820
+ }
821
+ if (v.primaryProvider === "dashscope" && v.dashscopePlan === "token") {
822
+ def.baseUrl = "https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1";
823
+ }
824
+ if (v.primaryProvider === "7yi" && (v.customBaseUrl || "").trim()) {
825
+ def.baseUrl = v.customBaseUrl.trim();
826
+ }
827
+ if (v.providerModels && v.providerModels.length > 0) {
828
+ def.models = v.providerModels.map((m) => ({
829
+ id: m.id,
830
+ name: m.name || m.id,
831
+ reasoning: m.reasoning ?? true,
832
+ // 没标 vision 的条目按「不支持」写 input(目录是权威),但 visionOf 那边对
833
+ // 「没标」仍按未知处理——若用户手输的型号恰好是主模型,vision 仍会宽复用它
834
+ input: m.vision ? ["text", "image"] : ["text"],
835
+ contextWindow: m.contextWindow ?? 2e5,
836
+ maxTokens: m.maxTokens ?? 8192
837
+ }));
838
+ }
839
+ const primaryModelId2 = modelIdOf(v.primaryModel);
840
+ if (primaryModelId2 && !def.models.some((m) => m?.id === primaryModelId2)) {
841
+ const old = (v.existingModels || []).find((m) => m?.id === primaryModelId2);
842
+ def.models = [...def.models, old || { id: primaryModelId2, name: primaryModelId2 }];
843
+ }
844
+ config.models.providers[v.primaryProvider] = {
845
+ ...def,
846
+ apiKey: "env:" + v.primaryProvider.toUpperCase().replace(/[^A-Z0-9_]/g, "_") + "_API_KEY",
847
+ ...def.baseUrl ? { baseUrl: def.baseUrl.trim() } : {}
848
+ };
849
+ }
850
+ const providerKeys = Object.keys(config.models.providers);
851
+ if (providerKeys.length === 0) {
852
+ throw new Error("\u6CA1\u6709\u751F\u6210\u4EFB\u4F55 provider\u2014\u2014\u68C0\u67E5 init \u7684 provider \u9009\u62E9\u4E0E\u6A21\u578B ID\u3002");
853
+ }
854
+ const primaryRef = String(config.agents.defaults.model.primary);
855
+ const primaryProviderId = primaryRef.split("/")[0];
856
+ if (!config.models.providers[primaryProviderId]) {
857
+ throw new Error(
858
+ `\u751F\u6210\u7684 config \u4E0D\u81EA\u6D3D\uFF1Aagents.defaults.model.primary = "${primaryRef}"\uFF0C\u4F46 models.providers \u91CC\u53EA\u6709 ${providerKeys.join(", ")}\u2014\u2014\u5F15\u64CE\u542F\u52A8\u4F1A\u76F4\u63A5\u629B Provider not found\u3002\u8FD9\u662F init \u7684 bug\uFF0C\u8BF7\u8FDE\u540C\u8FD9\u6761\u6D88\u606F\u4E00\u8D77\u53CD\u9988\u3002`
859
+ );
860
+ }
861
+ const primaryModelId = primaryRef.split("/").slice(1).join("/");
862
+ const listed = config.models.providers[primaryProviderId].models || [];
863
+ if (primaryModelId && !listed.some((m) => m?.id === primaryModelId)) {
864
+ throw new Error(
865
+ `\u751F\u6210\u7684 config \u4E0D\u81EA\u6D3D\uFF1A\u4E3B\u6A21\u578B "${primaryRef}" \u4E0D\u5728 ${primaryProviderId} \u7684\u6A21\u578B\u8868\u91CC\uFF08\u8868\u91CC\u662F ${listed.map((m) => m?.id).join(", ") || "\u7A7A"}\uFF09\u3002\u8FD9\u662F init \u7684 bug\uFF0C\u8BF7\u8FDE\u540C\u8FD9\u6761\u6D88\u606F\u4E00\u8D77\u53CD\u9988\u3002`
866
+ );
867
+ }
868
+ if (v.visionModel) {
869
+ const visionProvider = v.visionModel.split("/")[0];
870
+ if (!config.models.providers[visionProvider]) {
871
+ console.warn(
872
+ `[config] \u5FFD\u7565 vision \u5F15\u7528 "${v.visionModel}"\uFF1Aprovider "${visionProvider}" \u4E0D\u5728\u672C\u6B21\u751F\u6210\u7684 providers\uFF08${Object.keys(config.models.providers).join(", ") || "\u7A7A"}\uFF09\u91CC\u2014\u2014\u5199\u8FDB\u53BB\u4F1A\u8BA9 loader \u89E3\u6790\u5931\u8D25\u3001\u5165\u7AD9\u56FE\u7247\u88AB\u9759\u9ED8\u4E22\u5F03\u3002\u56FE\u7247\u8BC6\u522B\u672C\u6B21\u5173\u95ED\u3002`
873
+ );
874
+ delete config.agents.defaults.model.vision;
875
+ delete config.tools.my_eyes;
876
+ }
877
+ }
878
+ return config;
879
+ }
880
+ var SCHEMA_VERSION, FREE_TIER_RELAY_URL, DEFAULT_PRIMARY_MODEL, VISION_CAPABLE, PROVIDER_DEFS, MODEL_CHOICES, CUSTOM_DEFAULT_INPUT, TEMPLATE_VALUES;
881
+ var init_cli_init_config = __esm({
882
+ "src/cli-init-config.ts"() {
883
+ "use strict";
884
+ SCHEMA_VERSION = 1;
885
+ FREE_TIER_RELAY_URL = "https://www.twinsun.top:8866";
886
+ DEFAULT_PRIMARY_MODEL = {
887
+ dashscope: "dashscope/qwen3.8-max",
888
+ minimax: "minimax/MiniMax-M3",
889
+ zhipu: "zhipu/glm-5.3",
890
+ deepseek: "deepseek/deepseek-v4-pro",
891
+ "7yi": "7yi/engine7-free"
892
+ };
893
+ VISION_CAPABLE = {
894
+ // 0917:原先标 true,但同一个文件里 qwen3.8-max 的 input 是 ['text'],安装器目录
895
+ // 也明确标 vision=false(只有 Flash 叫"多模态")。矛盾直接导致**默认 CLI 装机路径**
896
+ // 重演 P0-2 的症状:vision 复用主模型 → 该型号 modelInputs=['text'] → 引擎把历史和
897
+ // 当前消息里的图全剥成 "[image]"。改成 false 后 resolveVisionModel 会落到
898
+ // qwen3.8-flash(input 真含 image)。
899
+ "dashscope/qwen3.8-max": false,
900
+ "dashscope/qwen3.8-flash": true,
901
+ "minimax/MiniMax-M3": true,
902
+ // 0918 移除 'minimax/MiniMax-M2.7'(内置表同时去掉该型号)
903
+ "zhipu/glm-5v-turbo": true,
904
+ "zhipu/glm-5.3": false,
905
+ // 0917:DeepSeek 全线按支持图片处理。依据(官方更新日志 + Vision 文档):
906
+ // - `deepseek-flash`(V4.1 Flash)原生多模态,官方 Vision 文档明确收图
907
+ // https://api-docs.deepseek.com/guides/vision
908
+ // - `deepseek-v4-flash` / `deepseek-v4-flash-vision-exp`:退役名,请求由 V4.1 Flash
909
+ // 承接(临时兼容路由,官方未给移除日期)→ 同样收图
910
+ // - `deepseek-v4-pro`:官方旗舰已停,9/14 起请求同样由 V4.1 Flash 承接;新的
911
+ // deepseek-pro 发布后大概率也走多模态(flash 都支持了,pro 不会倒退)
912
+ // 原先两个都标 false:v4-pro 标 false 会让今天用它的 agent **静默丢图**,
913
+ // deepseek-flash 标 false 还会触发 P0-2 的幽灵引用兜底。
914
+ // 声明多一分的代价是上游响亮报错,少一分的代价是无声丢图——选前者。
915
+ "deepseek/deepseek-flash": true,
916
+ "deepseek/deepseek-v4-flash": true,
917
+ "deepseek/deepseek-v4-pro": true,
918
+ // 免费档(0919):engine7-free 只收文本,engine7-vis-free 才带 image。
919
+ // 标错会让主模型被当成能看图 → 图发给一个 input=['text'] 的型号 → 静默丢图。
920
+ "7yi/engine7-free": false,
921
+ "7yi/engine7-vis-free": true
922
+ };
923
+ PROVIDER_DEFS = {
924
+ dashscope: {
925
+ // 双通道(0916):按量付费(sk-)走 compatible-mode;Token Plan 订阅(sk-sp-)走专属域名,
926
+ // 两边密钥/端点不可互换——用错就"订阅了还按量计费"。计划选择见 dashscopePlan。
927
+ baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",
928
+ api: "openai-completions",
929
+ models: [
930
+ { id: "qwen3.8-max", name: "Qwen 3.8 Max", reasoning: true, input: ["text"], contextWindow: 1e6, maxTokens: 65536 },
931
+ { id: "qwen3.8-flash", name: "Qwen 3.8 Flash\uFF08\u591A\u6A21\u6001\uFF09", reasoning: true, input: ["text", "image"], contextWindow: 1e6, maxTokens: 65536 }
932
+ ]
933
+ },
934
+ minimax: {
935
+ baseUrl: "https://api.minimaxi.com/anthropic",
936
+ api: "anthropic",
937
+ models: [
938
+ // 0918 去掉 MiniMax-M2.7:有 M3 之后没人用了(翀哥)
939
+ { id: "MiniMax-M3", name: "MiniMax M3", reasoning: true, input: ["text", "image"], contextWindow: 1e6, maxTokens: 64e3 }
940
+ ]
941
+ },
942
+ zhipu: {
943
+ baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
944
+ api: "openai-completions",
945
+ models: [
946
+ { id: "glm-5.3", name: "GLM-5.3 (\u6700\u65B0\u4E3B\u529B)", reasoning: true, input: ["text"], contextWindow: 1e6, maxTokens: 128e3 },
947
+ { id: "glm-5v-turbo", name: "GLM-5V-Turbo (\u89C6\u89C9)", reasoning: true, input: ["text", "image"], contextWindow: 2e5, maxTokens: 128e3 }
948
+ ]
949
+ },
950
+ deepseek: {
951
+ baseUrl: "https://api.deepseek.com/anthropic",
952
+ api: "anthropic",
953
+ thinking: { enabled: true, budgetTokens: 8192 },
954
+ // 0923:官方 anthropic 端点 thinking 模式下,历史里"带 tool_use 却没 thinking 块"的 assistant 轮
955
+ // 会让下一次请求 400 must be passed back。这种轮是引擎自己造的——空响应重试会发
956
+ // thinking:{type:'disabled'}(见 query.ts)。开了这个开关就给它补占位块、并让无签名的
957
+ // thinking 也回传。实测矩阵见 anthropic-provider.ts 的 AnthropicProviderConfig。
958
+ requireThinkingBlocks: true,
959
+ models: [
960
+ // 0916 改名:deepseek-v4-flash → deepseek-flash(旧名仍可调,请求由 V4.1 Flash 承接)
961
+ // 0917 补 image:V4.1 Flash 原生多模态(见 VISION_CAPABLE 注释)
962
+ { id: "deepseek-flash", name: "DeepSeek Flash", reasoning: true, input: ["text", "image"], contextWindow: 1e6, maxTokens: 384e3 },
963
+ // 0917 补 image:官方旗舰已停、9/14 起请求由 V4.1 Flash 承接,实际收图
964
+ { id: "deepseek-v4-pro", name: "DeepSeek V4 Pro", reasoning: true, input: ["text", "image"], contextWindow: 1e6, maxTokens: 384e3 }
965
+ ]
966
+ },
967
+ "7yi": {
968
+ // 引擎免费档的中转站。baseUrl 是兜底值——真正的权威值来自领号响应
969
+ // (服务端按请求 Host 自报地址),由安装器作为 customBaseUrl 下发。
970
+ // 这样挪机器、换域名只改服务端,不用重发安装包。
971
+ //
972
+ // ⚠️ 0921 修:这里原本写的是 `https://www.twinsun.top/v1`(443),而 **443 上不是
973
+ // 中转站**(实测回的是另一个服务的 FastAPI 404)。安装器那条路每次都会下发
974
+ // customBaseUrl 把它盖掉,所以正式装机撞不到;但**终端向导选 7yi 时不问 baseUrl**,
975
+ // 直接落到这个兜底值——于是命令行用户拿到的是「号领到了、引擎起来了、一发消息就废」。
976
+ // 用常量拼,别再手写一遍(漏端口就是这个坑)。
977
+ baseUrl: `${FREE_TIER_RELAY_URL}/v1`,
978
+ api: "anthropic",
979
+ models: [
980
+ // contextWindow 拍板 500000(0922 翀哥),两条依据:
981
+ // 1. token 估算有偏差——声明 500k → 压缩阈值约 42 万(×0.906 减 overhead),
982
+ // 就算估算再偏低一截,离 1M 真实窗口还留着 200k+ 余量,不会被 context overflow 掉。
983
+ // 2. 免费档烧的是中转站背后的 token,500k 上限封住单 session 消耗,够用了。
984
+ //
985
+ // 病例史(改这个值前必读):
986
+ // - 0919(声明 1M 的代价):阈值 906137 高到永不触发,上下文滚到 191080 仍不压缩,
987
+ // 191k 的请求把 SenseNova 的 TPM 打爆(429 inference exceeds tpm/rpm limit),
988
+ // 整条链退到付费的 MiniMax。当时据此取过 150000(实测成功线 114K)。
989
+ // - 0922(声明 150k 的代价,反向):把 7yi 只当 vision 旁路的部署(小柯)里,
990
+ // vision 引擎按 150k 算出阈值 49071,对共享 session 的 50 万 token(1M 主模型
991
+ // glm-5.3 的正常水位)连触 3 次 compact,47 万历史被 ruleCompact 折叠、不可逆。
992
+ // → **7yi 当 vision 旁路的部署,注册值必须跟主模型一致(1M),别用本条的 500k**;
993
+ // 本条 500k 只服务"7yi 当主模型"的装机场景。
994
+ { id: "engine7-free", name: "Engine7 \u514D\u8D39\uFF08\u6587\u672C\uFF09", reasoning: true, input: ["text"], contextWindow: 5e5, maxTokens: 64e3 },
995
+ // input 带 image:实测 Claude 格式的图片块经 /v1/messages 能被上游正确识别
996
+ { id: "engine7-vis-free", name: "Engine7 \u514D\u8D39\uFF08\u89C6\u89C9\uFF09", reasoning: true, input: ["text", "image"], contextWindow: 5e5, maxTokens: 64e3 }
997
+ // 注意:engine7-img-free 不在这里。它在 chat 端点是 404(不是 chat 模型),
998
+ // 生图由 my_selfie 自己拼 /v1/images/edits,relayModel 默认值已经是它。
999
+ ]
1000
+ }
1001
+ };
1002
+ MODEL_CHOICES = Object.fromEntries(
1003
+ Object.entries(PROVIDER_DEFS).map(([pid, def]) => [
1004
+ pid,
1005
+ def.models.map((m) => `${pid}/${m.id}`)
1006
+ ])
1007
+ );
1008
+ CUSTOM_DEFAULT_INPUT = ["text", "image"];
1009
+ TEMPLATE_VALUES = {
1010
+ stateDir: "{{STATE_DIR}}",
1011
+ timezone: "{{TIMEZONE}}",
1012
+ agentName: "{{AGENT_NAME}}",
1013
+ persona: "assistant",
1014
+ chatMode: "auto",
1015
+ primaryProvider: "dashscope",
1016
+ zhipuPlan: "",
1017
+ dashscopePlan: "",
1018
+ providerModels: null,
1019
+ // 走内置模型表,模板里展示内置形状
1020
+ primaryApiKey: "",
1021
+ // generateConfig 只写 env: 占位符,明文不落 config
1022
+ primaryModel: "dashscope/qwen3.8-max",
1023
+ // 派生而非手写:qwen3.8-max 不支持图片,vision 落到 flash。手写就会再制造一次
1024
+ // "vision 指向一个 input 里没有 image 的型号"(见 VISION_CAPABLE 注释)。
1025
+ visionModel: resolveVisionModel("dashscope", "dashscope/qwen3.8-max"),
1026
+ discordEnabled: false,
1027
+ discordToken: "",
1028
+ discordUserId: "",
1029
+ feishuEnabled: true,
1030
+ feishuAppId: "{{FEISHU_APP_ID}}",
1031
+ feishuAppSecret: "{{FEISHU_APP_SECRET}}",
1032
+ feishuOpenId: "",
1033
+ wechatEnabled: false,
1034
+ // 微信块是条件写入的,模板只展示通用形状
1035
+ wechatToken: "",
1036
+ wechatAccountId: "",
1037
+ customBaseUrl: "",
1038
+ customModelId: "",
1039
+ customApi: "anthropic",
1040
+ customProviderId: "",
1041
+ // 用 env: 而不是 {{}}:{{}} 是脚手架占位符({{STATE_DIR}} 那种,由工具替换),
1042
+ // 而这一格真实 init 写的就是个 key。写成 {{TAVILY_API_KEY}} 会误导人以为有人会替换它。
1043
+ // env:TAVILY_API_KEY 是引擎真认的形式(loader 会解析),照抄也能跑通。
1044
+ tavilyKey: "env:TAVILY_API_KEY",
1045
+ apiPort: 16990
1046
+ };
1047
+ }
1048
+ });
1049
+
1050
+ // src/config/secrets-env.ts
1051
+ import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
1052
+ import { basename as basename2, dirname as dirname2, join as join3 } from "node:path";
1053
+ function secretsHome() {
1054
+ return process.env.HOME || process.env.USERPROFILE || ".";
1055
+ }
1056
+ function loadSecretsEnv(stateDir, configName) {
1057
+ const home = secretsHome();
1058
+ const candidates = [
1059
+ join3(home, ".engine7-secrets", `${configName}.env`),
1060
+ join3(stateDir, "configs", `.env.${configName}`),
1061
+ join3(stateDir, "configs", ".env")
1062
+ ];
1063
+ for (const p of candidates) {
1064
+ if (!existsSync3(p)) continue;
1065
+ for (const line of readFileSync3(p, "utf-8").split("\n")) {
1066
+ const t = line.trim();
1067
+ if (!t || t.startsWith("#")) continue;
1068
+ const eq = t.indexOf("=");
1069
+ if (eq <= 0) continue;
1070
+ const k = t.slice(0, eq).trim();
1071
+ const v = t.slice(eq + 1).trim();
1072
+ const cur = process.env[k];
1073
+ if (k && (cur === void 0 || cur === "")) process.env[k] = v;
1074
+ }
1075
+ return p;
1076
+ }
1077
+ return null;
1078
+ }
1079
+ function writeSecretsValue(configPath, envKey, value) {
1080
+ const configName = basename2(configPath, ".json");
1081
+ const dir = join3(secretsHome(), ".engine7-secrets");
1082
+ mkdirSync2(dir, { recursive: true });
1083
+ const file2 = join3(dir, `${configName}.env`);
1084
+ const lines = [];
1085
+ if (existsSync3(file2)) {
1086
+ for (const line of readFileSync3(file2, "utf8").split(/\r?\n/)) {
1087
+ if (new RegExp(`^\\s*${envKey}\\s*=`).test(line)) continue;
1088
+ lines.push(line);
1089
+ }
1090
+ }
1091
+ while (lines.length && lines[lines.length - 1] === "") lines.pop();
1092
+ lines.push(`${envKey}=${value}`);
1093
+ writeFileSync2(file2, lines.join("\n") + "\n", { mode: 384 });
1094
+ try {
1095
+ chmodSync(file2, 384);
1096
+ } catch {
1097
+ }
1098
+ return file2;
1099
+ }
1100
+ var init_secrets_env = __esm({
1101
+ "src/config/secrets-env.ts"() {
1102
+ "use strict";
1103
+ }
1104
+ });
1105
+
1106
+ // src/config/display.ts
1107
+ function loadDisplayConfig(raw) {
1108
+ if (!raw) return { ...DEFAULT_DISPLAY };
1109
+ return {
1110
+ thinking: {
1111
+ ...DEFAULT_DISPLAY.thinking,
1112
+ ...raw.thinking || {}
1113
+ },
1114
+ toolUse: {
1115
+ ...DEFAULT_DISPLAY.toolUse,
1116
+ ...raw.toolUse || {}
1117
+ },
1118
+ toolResult: {
1119
+ ...DEFAULT_DISPLAY.toolResult,
1120
+ ...raw.toolResult || {}
1121
+ },
1122
+ reactions: {
1123
+ ...DEFAULT_DISPLAY.reactions,
1124
+ ...raw.reactions || {}
1125
+ },
1126
+ preview: {
1127
+ ...DEFAULT_DISPLAY.preview,
1128
+ ...raw.preview || {}
1129
+ },
1130
+ typing: {
1131
+ ...DEFAULT_DISPLAY.typing,
1132
+ ...raw.typing || {}
1133
+ }
1134
+ };
1135
+ }
1136
+ var DEFAULT_DISPLAY;
1137
+ var init_display = __esm({
1138
+ "src/config/display.ts"() {
1139
+ "use strict";
1140
+ DEFAULT_DISPLAY = {
1141
+ thinking: { enabled: true, emoji: "\u{1F4AD}", maxLen: 300 },
1142
+ toolUse: { enabled: true, emoji: "\u{1F527}", displayMode: "raw", bashDisplayMode: "description", displayLimits: { write: 200 } },
1143
+ toolResult: { enabled: true, emoji: "\u{1F4E4}" },
1144
+ reactions: { enabled: true, start: "\u{1F440}", done: "\u2705", error: "\u274C" },
1145
+ preview: { enabled: true, agentName: "Agent" },
1146
+ typing: { enabled: true }
1147
+ };
1148
+ }
1149
+ });
1150
+
1151
+ // src/config/features.ts
1152
+ function getFeature(key) {
1153
+ const v = liveConfig.get(`agents.defaults.features.${key}`);
1154
+ return v === void 0 ? FEATURE_DEFAULTS[key] : v;
1155
+ }
1156
+ var FEATURE_DEFAULTS;
1157
+ var init_features = __esm({
736
1158
  "src/config/features.ts"() {
737
1159
  "use strict";
738
1160
  init_live();
@@ -1386,9 +1808,9 @@ function isAutoMemPath(absolutePath, workspace) {
1386
1808
  return normalizedPath.startsWith(getAutoMemPath(workspace));
1387
1809
  }
1388
1810
  async function ensureMemoryDirExists(memoryDir) {
1389
- const fs32 = await import("node:fs");
1811
+ const fs33 = await import("node:fs");
1390
1812
  try {
1391
- await fs32.promises.mkdir(memoryDir, { recursive: true });
1813
+ await fs33.promises.mkdir(memoryDir, { recursive: true });
1392
1814
  } catch (e) {
1393
1815
  const code = e?.code;
1394
1816
  if (code !== "EEXIST") {
@@ -3070,7 +3492,7 @@ ${rawSample.join("\n")}`);
3070
3492
 
3071
3493
  // src/models/anthropic-provider.ts
3072
3494
  import { randomUUID } from "node:crypto";
3073
- var AnthropicProvider;
3495
+ var PLACEHOLDER_THINKING_TEXT, PLACEHOLDER_THINKING_SIGNATURE, MISSING_TOOL_RESULT_TEXT, AnthropicProvider;
3074
3496
  var init_anthropic_provider = __esm({
3075
3497
  "src/models/anthropic-provider.ts"() {
3076
3498
  "use strict";
@@ -3078,6 +3500,9 @@ var init_anthropic_provider = __esm({
3078
3500
  init_provider();
3079
3501
  init_think_stripper();
3080
3502
  init_well_formed();
3503
+ PLACEHOLDER_THINKING_TEXT = "(no reasoning recorded)";
3504
+ PLACEHOLDER_THINKING_SIGNATURE = "00000000-0000-4000-8000-000000000000";
3505
+ MISSING_TOOL_RESULT_TEXT = "[tool result missing: this tool call has no recorded result in history]";
3081
3506
  AnthropicProvider = class {
3082
3507
  // 模拟 CC 的 X-Claude-Code-Session-Id
3083
3508
  constructor(config) {
@@ -3092,11 +3517,15 @@ var init_anthropic_provider = __esm({
3092
3517
  name = "anthropic";
3093
3518
  baseUrl;
3094
3519
  sessionId;
3095
- /** Anthropic: system prompt 单独传,不在 messages 里 */
3096
- formatMessages(systemPrompt, messages) {
3520
+ /** Anthropic: system prompt 单独传,不在 messages 里
3521
+ * opts.thinkingEnabled:本次请求是否开着 thinking(streamChat 现算后传入)。
3522
+ * 只影响 requireThinkingBlocks 的占位块回填——那个约束只对 thinking 开启的请求成立。 */
3523
+ formatMessages(systemPrompt, messages, opts) {
3524
+ const requireThinking = this.config.requireThinkingBlocks ?? (opts?.model ? /deepseek/i.test(opts.model) : false);
3097
3525
  const formatted = [];
3098
3526
  for (const m of messages) {
3099
3527
  if (m.role === "system") continue;
3528
+ const thinkingBlock = m.role === "assistant" ? this.thinkingBlockFor(m, requireThinking) : null;
3100
3529
  if (m.role === "tool") {
3101
3530
  const toolBlock = {
3102
3531
  type: "tool_result",
@@ -3112,13 +3541,7 @@ var init_anthropic_provider = __esm({
3112
3541
  }
3113
3542
  } else if (m.role === "assistant" && m.tool_calls) {
3114
3543
  const content = [];
3115
- if (m.reasoning_content && m.thinking_signature) {
3116
- content.push({
3117
- type: "thinking",
3118
- thinking: m.reasoning_content,
3119
- signature: m.thinking_signature
3120
- });
3121
- }
3544
+ if (thinkingBlock) content.push(thinkingBlock);
3122
3545
  if (m.content) {
3123
3546
  const textContent = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
3124
3547
  content.push({ type: "text", text: textContent });
@@ -3137,12 +3560,12 @@ var init_anthropic_provider = __esm({
3137
3560
  });
3138
3561
  }
3139
3562
  formatted.push({ role: "assistant", content });
3140
- } else if (m.role === "assistant" && m.reasoning_content && m.thinking_signature) {
3563
+ } else if (m.role === "assistant" && thinkingBlock) {
3141
3564
  const textContent = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
3142
3565
  formatted.push({
3143
3566
  role: "assistant",
3144
3567
  content: [
3145
- { type: "thinking", thinking: m.reasoning_content, signature: m.thinking_signature },
3568
+ thinkingBlock,
3146
3569
  ...textContent ? [{ type: "text", text: textContent }] : []
3147
3570
  ]
3148
3571
  });
@@ -3190,18 +3613,116 @@ var init_anthropic_provider = __esm({
3190
3613
  repaired.push(msg2);
3191
3614
  }
3192
3615
  }
3616
+ const filled = this.fillMissingToolResults(repaired);
3617
+ if (filled.patched > 0) {
3618
+ console.log(`[anthropic] tool_result \u56DE\u586B ${filled.patched} \u4E2A assistant \u8F6E (0924 \u4E0A\u6E38\u8981\u6C42\u914D\u5BF9)`);
3619
+ }
3620
+ if (requireThinking && opts?.thinkingEnabled) {
3621
+ const patched = this.backfillMissingThinking(filled.messages);
3622
+ if (patched > 0) console.log(`[anthropic] thinking \u5757\u56DE\u586B ${patched} \u4E2A assistant \u8F6E (0923 \u4E0A\u6E38\u8981\u6C42)`);
3623
+ }
3193
3624
  return {
3194
- messages: repaired,
3625
+ messages: filled.messages,
3195
3626
  system: systemPrompt || void 0
3196
3627
  };
3197
3628
  }
3629
+ /**
3630
+ * 给"带 tool_use 却没有 tool_result"的 assistant 轮补合成结果(对齐 CC ensureToolResultPairing 第 3 步)。
3631
+ *
3632
+ * 上游的判据是**位置**:tool_result 必须出现在紧跟在 tool_use 之后的那条消息里
3633
+ * ("...blocks immediately after")。所以补两块:
3634
+ * - 下一条就是带 tool_result 的 user 轮 → 直接塞进那条轮
3635
+ * - 否则(正常对话的 user 轮 / 下一条是 assistant / 已是数组末尾)→ 插一条新的 user 轮
3636
+ * 标记 is_error:true —— 让模型知道这个工具**没跑成**,别把空白当"跑过了没输出"继续推。
3637
+ *
3638
+ * 前提:engine7 的 tool 结果永远紧跟它的 assistant 轮(provider 侧连续 tool 结果合并成一条
3639
+ * user 轮、ruleCompact 只整条删不搬家),所以不存在"结果在远处"的错位形态。真出现错位的
3640
+ * tool_use 也会被这里补上(下游那条远方的 tool_result 由第 2 步的孤儿判据处理)。
3641
+ */
3642
+ fillMissingToolResults(formatted) {
3643
+ const out = [];
3644
+ let patched = 0;
3645
+ for (let i = 0; i < formatted.length; i++) {
3646
+ const msg2 = formatted[i];
3647
+ out.push(msg2);
3648
+ if (msg2.role !== "assistant" || !Array.isArray(msg2.content)) continue;
3649
+ const useIds = msg2.content.filter((b) => b.type === "tool_use" && b.id).map((b) => b.id);
3650
+ if (useIds.length === 0) continue;
3651
+ const next = formatted[i + 1];
3652
+ const isUserNext = next?.role === "user";
3653
+ const nextResults = isUserNext && Array.isArray(next.content) ? next.content.filter((b) => b.type === "tool_result") : [];
3654
+ const have = new Set(nextResults.map((b) => b.tool_use_id));
3655
+ const missing = useIds.filter((id) => !have.has(id));
3656
+ if (missing.length === 0) continue;
3657
+ const synthetic = missing.map((id) => ({
3658
+ type: "tool_result",
3659
+ tool_use_id: id,
3660
+ content: MISSING_TOOL_RESULT_TEXT,
3661
+ is_error: true
3662
+ }));
3663
+ if (isUserNext) {
3664
+ const rest = Array.isArray(next.content) ? next.content : next.content ? [{ type: "text", text: String(next.content) }] : [];
3665
+ next.content = [...synthetic, ...rest];
3666
+ } else {
3667
+ out.push({ role: "user", content: synthetic });
3668
+ }
3669
+ patched++;
3670
+ }
3671
+ return { messages: out, patched };
3672
+ }
3673
+ /**
3674
+ * assistant 轮的 thinking 块该不该回传、带不带签名。
3675
+ * - 有签名 → 回传(带签名),官方 Anthropic 端点只认这条
3676
+ * - 无签名 → requireThinking 的端点回传真思考原文(0923 实测这类端点不校验签名值,丢掉纯亏);
3677
+ * 否则不回传(0827 的老行为,防官方端点 400)
3678
+ * - 压根没有 reasoning_content → 没有块可回(占位由 backfillMissingThinking 兜)
3679
+ * requireThinking 由 formatMessages 按三态算好传进来,这里只管形状。
3680
+ */
3681
+ thinkingBlockFor(m, requireThinking) {
3682
+ const rc = m.reasoning_content;
3683
+ if (!rc) return null;
3684
+ const sig = m.thinking_signature;
3685
+ if (sig) return { type: "thinking", thinking: rc, signature: sig };
3686
+ if (!requireThinking) return null;
3687
+ return { type: "thinking", thinking: rc, signature: PLACEHOLDER_THINKING_SIGNATURE };
3688
+ }
3689
+ /**
3690
+ * 给"带 tool_use 但整轮没有 thinking"的 assistant 轮补一个占位 thinking 块(0923)。
3691
+ *
3692
+ * 这种轮是引擎自己造出来的:query.ts 空响应重试会发 thinking:{type:'disabled'}(见该文件
3693
+ * "吃光输出预算 → retry with thinking off"),回来的轮只有 tool_use、没有 thinking。
3694
+ * 存进 jsonl 后,下一次请求把 thinking 开回来,上游就 400 must be passed back(实测复现)。
3695
+ *
3696
+ * 只补带 tool_use 的轮:纯文本 assistant 轮实测不触发该约束,少改一处就少一分风险。
3697
+ * **整段对话的第一个 assistant 轮也会被补**——实测首轮本来就是豁免的(不补也 200),
3698
+ * 但补上同样 200,为这一点加"是不是首轮"的判断不值得,统一补更好维护。
3699
+ * 用**赋值新数组**而不是 unshift——content 可能直接引用调用方的数组(下面的透传分支)。
3700
+ */
3701
+ backfillMissingThinking(formatted) {
3702
+ let patched = 0;
3703
+ for (const msg2 of formatted) {
3704
+ if (msg2.role !== "assistant" || !Array.isArray(msg2.content)) continue;
3705
+ if (!msg2.content.some((b) => b.type === "tool_use")) continue;
3706
+ if (msg2.content.some((b) => b.type === "thinking" || b.type === "redacted_thinking")) continue;
3707
+ msg2.content = [
3708
+ { type: "thinking", thinking: PLACEHOLDER_THINKING_TEXT, signature: PLACEHOLDER_THINKING_SIGNATURE },
3709
+ ...msg2.content
3710
+ ];
3711
+ patched++;
3712
+ }
3713
+ return patched;
3714
+ }
3198
3715
  /** Anthropic streaming: event: content_block_start/delta/stop */
3199
3716
  async *streamChat(params) {
3200
3717
  const stableText = params.systemStable || params.systemPrompt || "";
3201
3718
  const dynamicText = params.systemDynamic || "";
3719
+ const supportsThinking = !/haiku/i.test(params.model);
3720
+ const thinkingEnabled = !params.disableThinking && (this.config.thinking?.enabled ?? supportsThinking);
3721
+ const thinkingBudget = this.config.thinking?.budgetTokens || 8192;
3202
3722
  const { messages: formatted } = this.formatMessages(
3203
3723
  "",
3204
- params.messages
3724
+ params.messages,
3725
+ { thinkingEnabled, model: params.model }
3205
3726
  );
3206
3727
  const systemBlocks = [];
3207
3728
  if (stableText) {
@@ -3210,9 +3731,6 @@ var init_anthropic_provider = __esm({
3210
3731
  if (dynamicText) {
3211
3732
  systemBlocks.push({ type: "text", text: dynamicText });
3212
3733
  }
3213
- const supportsThinking = !/haiku/i.test(params.model);
3214
- const thinkingEnabled = !params.disableThinking && (this.config.thinking?.enabled ?? supportsThinking);
3215
- const thinkingBudget = this.config.thinking?.budgetTokens || 8192;
3216
3734
  const url = `${this.baseUrl}/v1/messages`;
3217
3735
  const body = {
3218
3736
  model: params.model,
@@ -3819,8 +4337,10 @@ function createProvider(config) {
3819
4337
  apiKey: config.apiKey,
3820
4338
  thinking: config.thinking,
3821
4339
  proxy,
3822
- wafSingleQuoteWorkaround: config.wafSingleQuoteWorkaround
4340
+ wafSingleQuoteWorkaround: config.wafSingleQuoteWorkaround,
3823
4341
  // 0902 agentrouter WAF 引号规避
4342
+ requireThinkingBlocks: config.requireThinkingBlocks
4343
+ // 0923 DeepSeek 官方端点要块不要签名
3824
4344
  });
3825
4345
  case "gemini":
3826
4346
  return new GeminiProvider({
@@ -5525,15 +6045,17 @@ function step2_compressOldTurns(messages, maxTokens, toolResultMode = "minimal")
5525
6045
  if (isRecent) {
5526
6046
  newMessages.push(...turn);
5527
6047
  } else {
5528
- const userMsg = turn[0];
5529
- const rest = turn.slice(1);
6048
+ const hasUserHead = turn[0].role === "user";
6049
+ const userMsg = hasUserHead ? turn[0] : null;
6050
+ const rest = hasUserHead ? turn.slice(1) : turn;
5530
6051
  const origMsgCount = turn.length;
5531
6052
  const origTokens = estimateMessageTokens(turn);
5532
- newMessages.push(userMsg);
6053
+ if (userMsg) newMessages.push(userMsg);
5533
6054
  if (toolResultMode === "inline") {
5534
6055
  const folded = foldTurnInline(turn);
5535
- newMessages.push(...folded.slice(1));
5536
- compactLog(TAG3, `Step 2 turn[${turnIdx}]: ${origMsgCount} msgs (${origTokens} tok) \u2192 ${1 + folded.length - 1} msgs (inline folded, mode=inline)`);
6056
+ newMessages.push(...hasUserHead ? folded.slice(1) : folded);
6057
+ const pushed = hasUserHead ? folded.length - 1 : folded.length;
6058
+ compactLog(TAG3, `Step 2 turn[${turnIdx}]: ${origMsgCount} msgs (${origTokens} tok) \u2192 ${(userMsg ? 1 : 0) + pushed} msgs (inline folded, mode=inline)`);
5537
6059
  } else {
5538
6060
  const kept = rest.filter((m) => m.role !== "tool").map((m) => {
5539
6061
  if (m.role === "assistant" && m.tool_calls && m.tool_calls.length > 0) {
@@ -5543,7 +6065,8 @@ function step2_compressOldTurns(messages, maxTokens, toolResultMode = "minimal")
5543
6065
  return m;
5544
6066
  }).filter((m) => m !== null);
5545
6067
  newMessages.push(...kept);
5546
- compactLog(TAG3, `Step 2 turn[${turnIdx}]: ${origMsgCount} msgs (${origTokens} tok) \u2192 ${1 + kept.length} msgs (user+${kept.length} text, tool stripped, mode=minimal)`);
6068
+ const keptDesc = userMsg ? `user+${kept.length} text` : `${kept.length} text (no user head)`;
6069
+ compactLog(TAG3, `Step 2 turn[${turnIdx}]: ${origMsgCount} msgs (${origTokens} tok) \u2192 ${(userMsg ? 1 : 0) + kept.length} msgs (${keptDesc}, tool stripped, mode=minimal)`);
5547
6070
  }
5548
6071
  compressedTurns++;
5549
6072
  }
@@ -5685,7 +6208,8 @@ function stripImageBlocks(messages) {
5685
6208
  }
5686
6209
  function getEffectiveContextWindowSize(config) {
5687
6210
  const contextWindow = config.contextWindow || DEFAULT_COMPACT_CONFIG.contextWindow;
5688
- return contextWindow - MAX_OUTPUT_TOKENS_FOR_SUMMARY;
6211
+ const reservedOutput = config.maxOutputTokens ?? DEFAULT_COMPACT_CONFIG.maxOutputTokens;
6212
+ return contextWindow - reservedOutput;
5689
6213
  }
5690
6214
  function getAutoCompactThreshold(config) {
5691
6215
  const effectiveWindow = getEffectiveContextWindowSize(config);
@@ -5825,7 +6349,7 @@ async function autoCompactIfNeeded(messages, config, tracking, provider, model,
5825
6349
  };
5826
6350
  }
5827
6351
  }
5828
- var TAG4, MAX_CONSECUTIVE_FAILURES, MAX_OUTPUT_TOKENS_FOR_SUMMARY, PRE_COMPACT_FLUSH_MESSAGE, PRE_COMPACT_FLUSH_MAX_TURNS;
6352
+ var TAG4, MAX_CONSECUTIVE_FAILURES, PRE_COMPACT_FLUSH_MESSAGE, PRE_COMPACT_FLUSH_MAX_TURNS;
5829
6353
  var init_autoCompact = __esm({
5830
6354
  "src/compact/autoCompact.ts"() {
5831
6355
  "use strict";
@@ -5839,7 +6363,6 @@ var init_autoCompact = __esm({
5839
6363
  init_compactLog();
5840
6364
  TAG4 = "autoCompact";
5841
6365
  MAX_CONSECUTIVE_FAILURES = 3;
5842
- MAX_OUTPUT_TOKENS_FOR_SUMMARY = 2e4;
5843
6366
  PRE_COMPACT_FLUSH_MESSAGE = "\u26A0\uFE0F Pre-compaction memory flush: \u4E0A\u4E0B\u6587\u5373\u5C06\u88AB\u538B\u7F29\u3002\n\n\u4F60\u5FC5\u987B\u7ACB\u5373\u6267\u884C\u4EE5\u4E0B\u64CD\u4F5C\uFF1A\n1. \u7528 write \u5DE5\u5177\u8986\u76D6\u66F4\u65B0 memory/working-buffer.md\uFF0C\u5199\u5165\u5F53\u524D\u6700\u65B0\u72B6\u6001\uFF08\u6B63\u5728\u505A\u4EC0\u4E48\u3001\u505A\u5230\u54EA\u4E86\u3001\u4E0B\u4E00\u6B65\u5177\u4F53\u52A8\u4F5C\uFF09\n2. \u5C06\u91CD\u8981\u4FE1\u606F\u8FFD\u52A0\u5230 memory/daily/YYYY-MM-DD.md\n\nworking-buffer.md \u662F\u6700\u9AD8\u4F18\u5148\u7EA7\u2014\u2014\u5B83\u4F1A\u88AB PostCompact hook \u81EA\u52A8\u6CE8\u5165\u5230\u538B\u7F29\u540E\u7684\u4E0A\u4E0B\u6587\u3002\u4E0D\u5199 = \u538B\u7F29\u540E\u5931\u5FC6\u3002";
5844
6367
  PRE_COMPACT_FLUSH_MAX_TURNS = 2;
5845
6368
  }
@@ -5860,18 +6383,88 @@ var init_compact2 = __esm({
5860
6383
  }
5861
6384
  });
5862
6385
 
5863
- // src/utils/path.ts
5864
- import * as path8 from "node:path";
5865
- function fixMsysPath(p) {
5866
- let fixed = p.includes("\\") ? p.replace(/\\/g, "/") : p;
5867
- if (process.platform === "win32") {
5868
- fixed = fixed.replace(/^\/([a-zA-Z])\//, (_, letter) => `${letter.toUpperCase()}:/`);
6386
+ // src/models/contextOverflow.ts
6387
+ function parseContextOverflowError(message) {
6388
+ if (!message) return void 0;
6389
+ for (const match of MATCHERS) {
6390
+ const info = match(message);
6391
+ if (!info) continue;
6392
+ if (!Number.isFinite(info.inputTokens) || !Number.isFinite(info.contextLimit)) continue;
6393
+ if (info.inputTokens <= 0 || info.contextLimit <= 0) continue;
6394
+ return info;
5869
6395
  }
5870
- return fixed;
6396
+ return void 0;
5871
6397
  }
5872
- function resolvePath(p, workspace) {
5873
- const fixed = fixMsysPath(p);
5874
- const resolved = path8.isAbsolute(fixed) ? path8.normalize(fixed) : path8.resolve(workspace, fixed);
6398
+ function computeAdjustedMaxTokens(info) {
6399
+ const available = info.contextLimit - info.inputTokens - CONTEXT_OVERFLOW_SAFETY_BUFFER;
6400
+ if (available < CONTEXT_OVERFLOW_FLOOR_TOKENS) return void 0;
6401
+ return available;
6402
+ }
6403
+ function fitMaxTokens(args) {
6404
+ const { window, estimatedInput, override } = args;
6405
+ const hasCap = typeof args.modelCap === "number" && args.modelCap > 0;
6406
+ const modelCap = hasCap ? args.modelCap : 0;
6407
+ if (override !== void 0 && override !== null && override > 0) {
6408
+ return hasCap ? Math.min(override, modelCap) : override;
6409
+ }
6410
+ if (!hasCap) return void 0;
6411
+ const fitted = window - estimatedInput - CONTEXT_OVERFLOW_SAFETY_BUFFER;
6412
+ if (fitted >= modelCap) return modelCap;
6413
+ return Math.max(fitted, CONTEXT_OVERFLOW_FLOOR_TOKENS);
6414
+ }
6415
+ var CONTEXT_OVERFLOW_SAFETY_BUFFER, CONTEXT_OVERFLOW_FLOOR_TOKENS, MATCHERS;
6416
+ var init_contextOverflow = __esm({
6417
+ "src/models/contextOverflow.ts"() {
6418
+ "use strict";
6419
+ CONTEXT_OVERFLOW_SAFETY_BUFFER = 1e3;
6420
+ CONTEXT_OVERFLOW_FLOOR_TOKENS = 3e3;
6421
+ MATCHERS = [
6422
+ // DeepSeek / vLLM / OpenAI 兼容:
6423
+ // "This model's maximum context length is 1048576 tokens. However, you requested 1048867
6424
+ // tokens (664867 in the messages, 384000 in the completion)."
6425
+ (m) => {
6426
+ const r = /maximum context length is\s*(\d+)\s*tokens[\s\S]{0,300}?\((\d+)\s*in the messages/i.exec(m);
6427
+ return r ? { contextLimit: Number(r[1]), inputTokens: Number(r[2]) } : void 0;
6428
+ },
6429
+ // OpenAI 老格式(只有实际用量,没有 requested 明细):
6430
+ // "This model's maximum context length is 8192 tokens. However your messages resulted in 9000 tokens."
6431
+ (m) => {
6432
+ const r = /maximum context length is\s*(\d+)\s*tokens[\s\S]{0,300}?resulted in\s*(\d+)\s*tokens/i.exec(m);
6433
+ return r ? { contextLimit: Number(r[1]), inputTokens: Number(r[2]) } : void 0;
6434
+ },
6435
+ // Anthropic 经典措辞:
6436
+ // "input length and `max_tokens` exceed context limit: 188059 + 20000 > 200000"
6437
+ (m) => {
6438
+ const r = /input length and\s+[`'"]?max_tokens[`'"]?\s+exceed context limit:\s*(\d+)\s*\+\s*(\d+)\s*>\s*(\d+)/i.exec(m);
6439
+ return r ? { inputTokens: Number(r[1]), contextLimit: Number(r[3]) } : void 0;
6440
+ },
6441
+ // Anthropic 新版措辞:
6442
+ // "prompt is too long: 210000 tokens > 200000 maximum"
6443
+ (m) => {
6444
+ const r = /prompt is too long:\s*(\d+)\s*tokens?\s*>\s*(\d+)\s*maximum/i.exec(m);
6445
+ return r ? { inputTokens: Number(r[1]), contextLimit: Number(r[2]) } : void 0;
6446
+ },
6447
+ // Gemini:"The input token count (1234) exceeds the maximum number of tokens allowed (1000)."
6448
+ (m) => {
6449
+ const r = /input token count\s*\((\d+)\)\s*exceeds the maximum number of tokens allowed\s*\((\d+)\)/i.exec(m);
6450
+ return r ? { inputTokens: Number(r[1]), contextLimit: Number(r[2]) } : void 0;
6451
+ }
6452
+ ];
6453
+ }
6454
+ });
6455
+
6456
+ // src/utils/path.ts
6457
+ import * as path8 from "node:path";
6458
+ function fixMsysPath(p) {
6459
+ let fixed = p.includes("\\") ? p.replace(/\\/g, "/") : p;
6460
+ if (process.platform === "win32") {
6461
+ fixed = fixed.replace(/^\/([a-zA-Z])\//, (_, letter) => `${letter.toUpperCase()}:/`);
6462
+ }
6463
+ return fixed;
6464
+ }
6465
+ function resolvePath(p, workspace) {
6466
+ const fixed = fixMsysPath(p);
6467
+ const resolved = path8.isAbsolute(fixed) ? path8.normalize(fixed) : path8.resolve(workspace, fixed);
5875
6468
  const normalized = path8.normalize(resolved);
5876
6469
  const normalizedWorkspace = path8.normalize(workspace);
5877
6470
  if (sandboxOff) return normalized;
@@ -7300,6 +7893,7 @@ var init_query = __esm({
7300
7893
  init_registry();
7301
7894
  init_types();
7302
7895
  init_compact2();
7896
+ init_contextOverflow();
7303
7897
  init_deferred();
7304
7898
  init_constants();
7305
7899
  init_deferred();
@@ -7410,6 +8004,27 @@ var init_query = __esm({
7410
8004
  resolveOpt(v) {
7411
8005
  return typeof v === "function" ? v() : v;
7412
8006
  }
8007
+ /**
8008
+ * 本次请求实发的 max_tokens(0923 规则1:按剩余空间现算,不按模型上限全额要)
8009
+ *
8010
+ * 配置里的 maxTokens 是**模型上限**,不是"每次都要预留这么多"。provider 的闸门是
8011
+ * `输入 + max_tokens ≤ 窗口`,按上限全额要输出会把输入空间挤没 —— 小雯事故:
8012
+ * 1,048,576 − 384,000 后输入只剩 664,576,而压缩阈值按 90 万算,**阈值落在墙外面**,
8013
+ * 压缩永远触发不了,于是每条消息都 400、每次重试发的还是同一个值,自锁。
8014
+ *
8015
+ * 现算 `窗口 − 估算输入 − 安全垫`,夹在 [下限, 模型上限] 之间。
8016
+ * 估算本身偏保守(CJK 按 1 字 1 token + 4/3 pad,小雯实测高估 ~29%),所以算出来只会更小、
8017
+ * 不会更险;真算错了还有规则3 兜底(被拒后拿 API 报的真实数字重算,见 models/contextOverflow.ts)。
8018
+ */
8019
+ resolveRequestMaxTokens(messages, override) {
8020
+ const cap = this.resolveOpt(this.options.maxTokens);
8021
+ return fitMaxTokens({
8022
+ modelCap: cap,
8023
+ window: this.compactConfig.contextWindow || DEFAULT_COMPACT_CONFIG.contextWindow,
8024
+ estimatedInput: estimateMessageTokens(messages) + (this.systemOverheadTokens ?? 0),
8025
+ override
8026
+ });
8027
+ }
7413
8028
  /** 热加载:替换 provider 实例(doReloadConfig 重建 provider 链后调用) */
7414
8029
  setProvider(provider) {
7415
8030
  this.provider = provider;
@@ -7480,6 +8095,8 @@ var init_query = __esm({
7480
8095
  let turnCount = 0;
7481
8096
  let toolCalls = [];
7482
8097
  let streamRetries = 0;
8098
+ let maxTokensOverride = null;
8099
+ let overflowAdjustments = 0;
7483
8100
  let vcFirstToolAt = null;
7484
8101
  let turnThinking = "";
7485
8102
  let turnThinkingSig = "";
@@ -7613,7 +8230,7 @@ ${perTurnSystemDynamic}` : deferredHint || perTurnSystemDynamic;
7613
8230
  systemStable: activeSystemStable,
7614
8231
  systemDynamic: fullSystemDynamic,
7615
8232
  tools: toolDefs.length > 0 ? toolDefs : void 0,
7616
- maxTokens: typeof this.options.maxTokens === "function" ? this.options.maxTokens() : this.options.maxTokens,
8233
+ maxTokens: this.resolveRequestMaxTokens(apiMessages, maxTokensOverride),
7617
8234
  temperature: this.options.temperature,
7618
8235
  signal: ac.signal,
7619
8236
  disableThinking: this.options.disableThinking || !!perTurnEmotion
@@ -7679,6 +8296,23 @@ ${perTurnSystemDynamic}` : deferredHint || perTurnSystemDynamic;
7679
8296
  }
7680
8297
  if (streamError) {
7681
8298
  const MAX_STREAM_RETRIES = 3;
8299
+ const overflow = parseContextOverflowError(streamError);
8300
+ if (overflow) {
8301
+ const adjusted = computeAdjustedMaxTokens(overflow);
8302
+ if (adjusted !== void 0 && overflowAdjustments < 3 && !ac.signal.aborted) {
8303
+ maxTokensOverride = maxTokensOverride !== null ? Math.min(maxTokensOverride, adjusted) : adjusted;
8304
+ overflowAdjustments++;
8305
+ console.log(`${this.logPrefix} Turn ${turnCount}: context overflow\uFF08API \u62A5 \u8F93\u5165=${overflow.inputTokens} / \u7A97\u53E3=${overflow.contextLimit}\uFF09\u2192 \u91CD\u7B97 max_tokens=${maxTokensOverride} \u540E\u91CD\u8BD5`);
8306
+ yield { type: "status", text: `\u26A0\uFE0F \u4E0A\u4E0B\u6587\u8D85\u9650\uFF0C\u6539\u7528 ${maxTokensOverride} \u8F93\u51FA\u9884\u7B97\u91CD\u8BD5...` };
8307
+ turnThinking = "";
8308
+ turnThinkingSig = "";
8309
+ turnResponsesReasoning = "";
8310
+ continue;
8311
+ }
8312
+ console.error(`${this.logPrefix} Turn ${turnCount}: context overflow \u4E14\u91CD\u7B97\u540E\u4E0D\u8DB3\uFF08\u8F93\u5165=${overflow.inputTokens} / \u7A97\u53E3=${overflow.contextLimit}\uFF09\u2192 \u653E\u5F03\u91CD\u8BD5`);
8313
+ yield { type: "error", error: `\u4E0A\u4E0B\u6587\u8D85\u51FA\u6A21\u578B\u7A97\u53E3\uFF08\u8F93\u5165 ${overflow.inputTokens} / \u7A97\u53E3 ${overflow.contextLimit}\uFF09\uFF0C\u9700\u8981\u538B\u7F29\u540E\u624D\u80FD\u7EE7\u7EED\u3002` };
8314
+ return;
8315
+ }
7682
8316
  const delay = Math.min(1e3 * Math.pow(2, streamRetries), 16e3);
7683
8317
  if (streamRetries < MAX_STREAM_RETRIES && !ac.signal.aborted) {
7684
8318
  console.log(`${this.logPrefix} Turn ${turnCount}: stream retry ${streamRetries + 1}/${MAX_STREAM_RETRIES}, delay=${delay}ms`);
@@ -7707,7 +8341,7 @@ ${perTurnSystemDynamic}` : deferredHint || perTurnSystemDynamic;
7707
8341
  systemStable: activeSystemStable,
7708
8342
  systemDynamic: fullSystemDynamic,
7709
8343
  tools: toolDefs.length > 0 ? toolDefs : void 0,
7710
- maxTokens: typeof this.options.maxTokens === "function" ? this.options.maxTokens() : this.options.maxTokens,
8344
+ maxTokens: this.resolveRequestMaxTokens(apiMessages, maxTokensOverride),
7711
8345
  temperature: this.options.temperature,
7712
8346
  signal: ac.signal,
7713
8347
  ...thinkingAteBudget ? { disableThinking: true } : {}
@@ -7831,7 +8465,7 @@ ${perTurnSystemDynamic}` : deferredHint || perTurnSystemDynamic;
7831
8465
  for (const g of gatedCalls) {
7832
8466
  yield { type: "tool_result", tool: g.call.function?.name, tool_call_id: g.call.id, summary: g.text.slice(0, 200), isError: true };
7833
8467
  }
7834
- yield { type: "history", messages: [msg.assistant(textContent, toolCalls), ...blockResults] };
8468
+ yield { type: "history", messages: [gateAssistant, ...blockResults] };
7835
8469
  continue;
7836
8470
  }
7837
8471
  const vcPlaceholderOn = context?.channel === "voice-chat" && (liveConfig.get("voiceChat.placeholder.enabled") ?? true);
@@ -7915,10 +8549,16 @@ ${perTurnSystemDynamic}` : deferredHint || perTurnSystemDynamic;
7915
8549
  const interruptedResults = toolCalls.map(
7916
8550
  (tc) => msg.tool(tc.id, "[interrupted by user /stop]", false)
7917
8551
  );
8552
+ const interruptedAssistant = msg.assistant(finalText, toolCalls);
8553
+ if (turnThinking) {
8554
+ interruptedAssistant.reasoning_content = turnThinking;
8555
+ if (turnThinkingSig) interruptedAssistant.thinking_signature = turnThinkingSig;
8556
+ }
8557
+ if (turnResponsesReasoning) interruptedAssistant.responses_reasoning = turnResponsesReasoning;
7918
8558
  yield {
7919
8559
  type: "history",
7920
8560
  messages: [
7921
- msg.assistant(finalText, toolCalls),
8561
+ interruptedAssistant,
7922
8562
  ...interruptedResults
7923
8563
  ]
7924
8564
  };
@@ -8001,6 +8641,19 @@ ${perTurnSystemDynamic}` : deferredHint || perTurnSystemDynamic;
8001
8641
  import * as fs11 from "node:fs";
8002
8642
  import * as path12 from "node:path";
8003
8643
  import * as crypto2 from "node:crypto";
8644
+ function assistantWriteParamsFrom(m) {
8645
+ const anyM = m;
8646
+ const tc = anyM.tool_calls;
8647
+ return {
8648
+ text: typeof m.content === "string" ? m.content : "",
8649
+ // 三个通道各自独立判断。truthiness 即"有值"——跟 writer 只写真值 / reader 只认真值的约定一致
8650
+ // (空串等于没有,见本文件 ThinkingBlock 的落盘条件)。
8651
+ ...anyM.reasoning_content ? { thinking: anyM.reasoning_content } : {},
8652
+ ...anyM.thinking_signature ? { thinkingSignature: anyM.thinking_signature } : {},
8653
+ ...anyM.responses_reasoning ? { responsesReasoning: anyM.responses_reasoning } : {},
8654
+ ...tc?.length > 0 ? { toolCalls: tc } : {}
8655
+ };
8656
+ }
8004
8657
  var SessionWriter;
8005
8658
  var init_writer = __esm({
8006
8659
  "src/session/writer.ts"() {
@@ -8874,12 +9527,19 @@ var init_reader = __esm({
8874
9527
  // src/session/session-manager.ts
8875
9528
  var session_manager_exports = {};
8876
9529
  __export(session_manager_exports, {
8877
- SessionManager: () => SessionManager
9530
+ SessionManager: () => SessionManager,
9531
+ alignRestoreHeadToTurnBoundary: () => alignRestoreHeadToTurnBoundary
8878
9532
  });
8879
9533
  import * as path14 from "node:path";
8880
9534
  import * as fs13 from "node:fs";
8881
9535
  import * as crypto3 from "node:crypto";
8882
- var RESTORE_MAX_CHARS, CHARS_PER_TOKEN, SessionManager;
9536
+ function alignRestoreHeadToTurnBoundary(messages, minKeep = RESTORE_MIN_KEEP_MESSAGES) {
9537
+ const isTurnBoundary = (m) => isAttachmentMessage(m) ? true : m.role === "user";
9538
+ const headIdx = messages.findIndex(isTurnBoundary);
9539
+ if (headIdx <= 0 || messages.length - headIdx <= minKeep) return { messages, dropped: 0 };
9540
+ return { messages: messages.slice(headIdx), dropped: headIdx };
9541
+ }
9542
+ var RESTORE_MAX_CHARS, CHARS_PER_TOKEN, RESTORE_MIN_KEEP_MESSAGES, SessionManager;
8883
9543
  var init_session_manager = __esm({
8884
9544
  "src/session/session-manager.ts"() {
8885
9545
  "use strict";
@@ -8889,6 +9549,7 @@ var init_session_manager = __esm({
8889
9549
  init_reader();
8890
9550
  RESTORE_MAX_CHARS = 15e4;
8891
9551
  CHARS_PER_TOKEN = 3;
9552
+ RESTORE_MIN_KEEP_MESSAGES = 10;
8892
9553
  SessionManager = class {
8893
9554
  sessionsDir;
8894
9555
  indexPath;
@@ -9254,6 +9915,8 @@ var init_session_manager = __esm({
9254
9915
  const a = msg.assistant(m.content, toolCalls);
9255
9916
  if (m.timestamp) a.timestamp = m.timestamp;
9256
9917
  if (m.responses_reasoning) a.responses_reasoning = m.responses_reasoning;
9918
+ if (m.reasoning_content) a.reasoning_content = m.reasoning_content;
9919
+ if (m.thinking_signature) a.thinking_signature = m.thinking_signature;
9257
9920
  allMessages.push(a);
9258
9921
  } else if (m.role === "tool") {
9259
9922
  const t = msg.tool(m.tool_call_id || "", m.content);
@@ -9280,13 +9943,16 @@ var init_session_manager = __esm({
9280
9943
  if (allMessages.length === 0) return [];
9281
9944
  let messages = allMessages;
9282
9945
  const maxTokens = Math.floor(RESTORE_MAX_CHARS / CHARS_PER_TOKEN);
9283
- while (this.estimateTokens(messages) > maxTokens && messages.length > 10) {
9946
+ while (this.estimateTokens(messages) > maxTokens && messages.length > RESTORE_MIN_KEEP_MESSAGES) {
9284
9947
  messages = messages.slice(1);
9285
9948
  }
9949
+ const tokenDropped = allMessages.length - messages.length;
9950
+ const aligned = alignRestoreHeadToTurnBoundary(messages);
9951
+ messages = aligned.messages;
9952
+ const alignedNote = aligned.dropped > 0 ? ` (head-aligned: dropped ${aligned.dropped} more msg(s) to reach a turn boundary)` : "";
9286
9953
  const totalTokens = this.estimateTokens(messages);
9287
- const truncated = allMessages.length - messages.length;
9288
- const truncNote = truncated > 0 ? ` (token-truncated: ${truncated} older msgs dropped, max ${maxTokens} tok)` : "";
9289
- console.log(`[session] Restored ${messages.length} messages (~${totalTokens} tokens)${truncNote} from ${files.length} file(s)`);
9954
+ const truncNote = tokenDropped > 0 ? ` (token-truncated: ${tokenDropped} older msgs dropped, max ${maxTokens} tok)` : "";
9955
+ console.log(`[session] Restored ${messages.length} messages (~${totalTokens} tokens)${truncNote}${alignedNote} from ${files.length} file(s)`);
9290
9956
  return messages;
9291
9957
  }
9292
9958
  // === 启动时 rotate main session ===
@@ -11449,7 +12115,7 @@ async function acquireLock(inboxPath) {
11449
12115
  for (let i = 0; i < LOCK_RETRIES; i++) {
11450
12116
  if (await isLockStale(lockPath2)) {
11451
12117
  try {
11452
- await import("node:fs/promises").then((fs32) => fs32.rm(lockPath2, { force: true }));
12118
+ await import("node:fs/promises").then((fs33) => fs33.rm(lockPath2, { force: true }));
11453
12119
  } catch {
11454
12120
  }
11455
12121
  }
@@ -11457,7 +12123,7 @@ async function acquireLock(inboxPath) {
11457
12123
  await writeFile2(lockPath2, `${process.pid}-${Date.now()}`, { encoding: "utf-8", flag: "wx" });
11458
12124
  return async () => {
11459
12125
  try {
11460
- await import("node:fs/promises").then((fs32) => fs32.rm(lockPath2, { force: true }));
12126
+ await import("node:fs/promises").then((fs33) => fs33.rm(lockPath2, { force: true }));
11461
12127
  } catch {
11462
12128
  }
11463
12129
  };
@@ -12858,8 +13524,8 @@ Examples:
12858
13524
  if (!to && !resolvedChannelId) {
12859
13525
  return { content: "to \u548C channel_id \u4E0D\u80FD\u540C\u65F6\u4E3A\u7A7A\uFF0C\u4E14\u6CA1\u6709\u53EF\u7528\u7684\u6765\u6E90\u9891\u9053", isError: true };
12860
13526
  }
12861
- const fs32 = await import("node:fs");
12862
- if (!fs32.existsSync(filePath)) {
13527
+ const fs33 = await import("node:fs");
13528
+ if (!fs33.existsSync(filePath)) {
12863
13529
  return { content: `\u53D1\u9001\u5931\u8D25: \u6587\u4EF6\u4E0D\u5B58\u5728 ${filePath}`, isError: true };
12864
13530
  }
12865
13531
  const toIds = to ? to.split(",").map((s) => s.trim()).filter(Boolean) : [];
@@ -12887,7 +13553,7 @@ Examples:
12887
13553
  md: "text/markdown"
12888
13554
  };
12889
13555
  const mimeType = mimeTypeMap[ext] || "application/octet-stream";
12890
- const stat4 = fs32.statSync(filePath);
13556
+ const stat4 = fs33.statSync(filePath);
12891
13557
  const sizeMB = stat4.size / 1024 / 1024;
12892
13558
  if (sizeMB > 25) {
12893
13559
  return { content: `\u53D1\u9001\u5931\u8D25: \u6587\u4EF6 ${sizeMB.toFixed(1)}MB \u8D85\u8FC7 Discord 25MB \u9650\u5236`, isError: true };
@@ -13944,9 +14610,9 @@ function registerPortrait(relPath, name) {
13944
14610
  const cur = cfg?.tools?.my_selfie?.references || cfg?.my_selfie?.references || [];
13945
14611
  const next = Array.isArray(cur) ? cur.filter((r) => r?.name !== name) : [];
13946
14612
  next.unshift({ name, path: relPath });
13947
- const path34 = cfg?.tools?.my_selfie ? "tools.my_selfie.references" : "my_selfie.references";
13948
- void liveConfig.set(path34, next);
13949
- console.log(`[my-selfie] \u5F62\u8C61\u5DF2\u767B\u8BB0\uFF1A${path34}[0] = ${relPath}`);
14613
+ const path35 = cfg?.tools?.my_selfie ? "tools.my_selfie.references" : "my_selfie.references";
14614
+ void liveConfig.set(path35, next);
14615
+ console.log(`[my-selfie] \u5F62\u8C61\u5DF2\u767B\u8BB0\uFF1A${path35}[0] = ${relPath}`);
13950
14616
  } catch (e) {
13951
14617
  console.warn(`[my-selfie] \u5199\u5165 references \u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u672C\u6B21\u51FA\u56FE\uFF09: ${e.message}`);
13952
14618
  }
@@ -17395,10 +18061,10 @@ function ensureLoaded(workspace, configIds) {
17395
18061
  if (!state.blockedUserIds.includes(id)) state.blockedUserIds.push(id);
17396
18062
  }
17397
18063
  }
17398
- const path34 = join25(workspace, ".reply-blocklist.json");
18064
+ const path35 = join25(workspace, ".reply-blocklist.json");
17399
18065
  try {
17400
- if (existsSync20(path34)) {
17401
- const raw = readFileSync20(path34, "utf-8");
18066
+ if (existsSync20(path35)) {
18067
+ const raw = readFileSync20(path35, "utf-8");
17402
18068
  const parsed = JSON.parse(raw);
17403
18069
  if (parsed.blockedUserIds) {
17404
18070
  for (const id of parsed.blockedUserIds) {
@@ -17414,9 +18080,9 @@ function ensureLoaded(workspace, configIds) {
17414
18080
  loaded = true;
17415
18081
  }
17416
18082
  function save(workspace) {
17417
- const path34 = join25(workspace, ".reply-blocklist.json");
18083
+ const path35 = join25(workspace, ".reply-blocklist.json");
17418
18084
  try {
17419
- writeFileSync15(path34, JSON.stringify(state, null, 2), "utf-8");
18085
+ writeFileSync15(path35, JSON.stringify(state, null, 2), "utf-8");
17420
18086
  } catch (err) {
17421
18087
  console.warn(`[reply-blocklist] Failed to save: ${err.message}`);
17422
18088
  }
@@ -18320,7 +18986,8 @@ function buildLightHistory(history, opts) {
18320
18986
  function buildLightStablePrompt(workspace, mode, opts) {
18321
18987
  const lp = liveConfig.get(`channels.${mode}.liveSystemPrompt`);
18322
18988
  const parts = [];
18323
- for (const f of lp?.files ?? ["SOUL.md"]) {
18989
+ const files = lp?.files ?? (mode === "emotion" ? ["SOUL.md", "EMOTION.md"] : ["SOUL.md"]);
18990
+ for (const f of files) {
18324
18991
  try {
18325
18992
  parts.push(readFileSync22(join29(workspace, f), "utf-8").trim());
18326
18993
  } catch {
@@ -18343,7 +19010,7 @@ function buildLightStablePrompt(workspace, mode, opts) {
18343
19010
  ].join("\n"));
18344
19011
  }
18345
19012
  const built = parts.join("\n\n");
18346
- console.log(`[light-stable] mode=${mode} files=[${(lp?.files ?? ["SOUL.md"]).join(",")}] ~${Math.round(built.length / 3)} tokens`);
19013
+ console.log(`[light-stable] mode=${mode} files=[${files.join(",")}] ~${Math.round(built.length / 3)} tokens`);
18347
19014
  return built;
18348
19015
  }
18349
19016
  var init_light_mode = __esm({
@@ -18802,7 +19469,9 @@ var init_system_filter = __esm({
18802
19469
  var ingest_exports = {};
18803
19470
  __export(ingest_exports, {
18804
19471
  buildPayload: () => buildPayload,
18805
- pushConversation: () => pushConversation
19472
+ filterMessages: () => filterMessages,
19473
+ pushConversation: () => pushConversation,
19474
+ toPayload: () => toPayload
18806
19475
  });
18807
19476
  import fs27 from "node:fs";
18808
19477
  import path28 from "node:path";
@@ -18859,45 +19528,57 @@ function savePushTs(workspace, sessionId, ts) {
18859
19528
  console.warn(`[everos-ingest] save pushTs failed: ${e?.message ?? e}`);
18860
19529
  }
18861
19530
  }
18862
- function buildPayload(messages, cfg, sessionId) {
19531
+ function contentToText(content) {
19532
+ if (typeof content === "string") return content;
19533
+ if (Array.isArray(content)) {
19534
+ const texts = content.filter((b) => b?.type === "text" && typeof b.text === "string").map((b) => b.text);
19535
+ return texts.length ? texts.join("\n") : "[content blocks]";
19536
+ }
19537
+ return "[content blocks]";
19538
+ }
19539
+ function filterMessages(messages, cfg) {
18863
19540
  let lastUserWasSystem = false;
19541
+ const out = [];
19542
+ for (const m of messages) {
19543
+ const raw = contentToText(m.content);
19544
+ const meta = m.role === "user" ? parseMeta(raw) : null;
19545
+ let text = raw;
19546
+ let isSystemTurn = false;
19547
+ if (m.role === "user") {
19548
+ if (isSystemUserMessage(parseMeta(raw))) {
19549
+ text = "";
19550
+ isSystemTurn = true;
19551
+ } else text = stripInjectedContext(raw.replace(/^\[meta:[^\]]*\]\s*/, ""));
19552
+ lastUserWasSystem = isSystemTurn;
19553
+ }
19554
+ if (m.role === "user" && !text || m.role !== "user" && lastUserWasSystem) continue;
19555
+ const senderName = meta?.senderName ?? (m.role === "assistant" ? cfg.agentName : void 0) ?? m.role;
19556
+ const rawRole = m.role === "toolResult" ? "tool" : m.role;
19557
+ const role = rawRole === "user" ? "user" : "assistant";
19558
+ const tsMs = typeof m.timestamp === "number" ? m.timestamp : Date.parse(String(m.timestamp ?? "")) || Date.now();
19559
+ out.push({
19560
+ // owner 口径见 IngestConfig.ownerMode:'sender' 模式下 user 消息用真人 ID
19561
+ // (everos 的 owner fan-out 取的就是 user 的 sender_id → 每人一份记忆)
19562
+ sender_id: m.role === "user" && cfg.ownerMode === "sender" ? meta?.senderId || cfg.appId : cfg.appId,
19563
+ sender_name: senderName,
19564
+ role,
19565
+ timestamp: tsMs,
19566
+ content: text
19567
+ });
19568
+ }
19569
+ return out;
19570
+ }
19571
+ function toPayload(messages, cfg, sessionId) {
18864
19572
  return {
18865
19573
  session_id: `extract-${sessionId}`,
18866
19574
  app_id: cfg.appId,
18867
19575
  project_id: "default",
18868
- // 0916:user 消息过 stripInjectedContext(sidecar/skill-gate 同款)——剥注入物后再推,
18869
- // 剥完为空的内部消息(心跳/nudge 注入)整条跳过。此前从未剥过,episode/foresight
18870
- // 一直吃着 460 字里只有 26 字原话的拼装体。
18871
- messages: messages.map((m) => {
18872
- const raw = typeof m.content === "string" ? m.content : "[content blocks]";
18873
- const meta = m.role === "user" ? parseMeta(raw) : null;
18874
- let text = raw;
18875
- let isSystemTurn = false;
18876
- if (m.role === "user") {
18877
- if (isSystemUserMessage(parseMeta(raw))) {
18878
- text = "";
18879
- isSystemTurn = true;
18880
- } else text = stripInjectedContext(raw.replace(/^\[meta:[^\]]*\]\s*/, ""));
18881
- lastUserWasSystem = isSystemTurn;
18882
- }
18883
- const senderName = meta?.senderName ?? (m.role === "assistant" ? cfg.agentName : void 0) ?? m.role;
18884
- const rawRole = m.role === "toolResult" ? "tool" : m.role;
18885
- const role = rawRole === "user" ? "user" : "assistant";
18886
- const tsMs = typeof m.timestamp === "number" ? m.timestamp : Date.parse(String(m.timestamp ?? "")) || Date.now();
18887
- return {
18888
- // owner 口径见 IngestConfig.ownerMode:'sender' 模式下 user 消息用真人 ID
18889
- // (everos 的 owner fan-out 取的就是 user 的 sender_id → 每人一份记忆)
18890
- sender_id: m.role === "user" && cfg.ownerMode === "sender" ? meta?.senderId || cfg.appId : cfg.appId,
18891
- sender_name: senderName,
18892
- role,
18893
- timestamp: tsMs,
18894
- content: text,
18895
- // 丢:系统指令本体 + 系统轮里的回应(与 mem7 同款,见上面状态机)
18896
- _skip: m.role === "user" && !text || m.role !== "user" && lastUserWasSystem
18897
- };
18898
- }).filter((x) => !x._skip).map(({ _skip, ...rest }) => rest)
19576
+ messages
18899
19577
  };
18900
19578
  }
19579
+ function buildPayload(messages, cfg, sessionId) {
19580
+ return toPayload(filterMessages(messages, cfg), cfg, sessionId);
19581
+ }
18901
19582
  async function fetchWithTimeout(url, options, timeoutMs) {
18902
19583
  const controller = new AbortController();
18903
19584
  let timer;
@@ -18961,17 +19642,24 @@ async function pushConversation(messages, sessionId, workspace) {
18961
19642
  if (ms > maxTs) maxTs = ms;
18962
19643
  }
18963
19644
  if (toPush.length === 0) return;
18964
- const total = toPush.length;
19645
+ const filtered = filterMessages(toPush, cfg);
19646
+ if (filtered.length === 0) {
19647
+ savePushTs(workspace, sessionId, maxTs);
19648
+ console.log("[everos-ingest] all filtered out (system turns only), advance pushTs only");
19649
+ return;
19650
+ }
19651
+ const total = filtered.length;
18965
19652
  const chunkTotal = Math.ceil(total / PUSH_CHUNK);
18966
19653
  let pushed = 0;
19654
+ let failedChunks = 0;
18967
19655
  for (let i = 0; i < total; i += PUSH_CHUNK) {
18968
- const chunk = toPush.slice(i, i + PUSH_CHUNK);
19656
+ const chunk = filtered.slice(i, i + PUSH_CHUNK);
18969
19657
  const chunkMaxTs = chunk.reduce((mx, m) => {
18970
- const ms = m.timestamp ? new Date(m.timestamp).getTime() : 0;
18971
- return Number.isNaN(ms) || ms < mx ? mx : ms;
19658
+ const ms = m.timestamp ?? 0;
19659
+ return ms < mx ? mx : ms;
18972
19660
  }, pushTs);
18973
19661
  const chunkNo = Math.floor(i / PUSH_CHUNK) + 1;
18974
- const payload = buildPayload(chunk, cfg, sessionId);
19662
+ const payload = toPayload(chunk, cfg, sessionId);
18975
19663
  console.log(`[everos-ingest] pushing chunk ${chunkNo}/${chunkTotal} (${chunk.length} msgs) to ${cfg.url} (timeout=${cfg.ingestTimeoutMs}ms)`);
18976
19664
  try {
18977
19665
  const resp = await fetchWithTimeout(`${cfg.url}/api/v1/memory/add`, {
@@ -18984,16 +19672,19 @@ async function pushConversation(messages, sessionId, workspace) {
18984
19672
  pushed += chunk.length;
18985
19673
  console.log(`[everos-ingest] \u2705 pushed chunk ${chunkNo}/${chunkTotal} (${chunk.length} msgs, pushTs\u2192${new Date(chunkMaxTs).toISOString().slice(11, 19)})`);
18986
19674
  } else {
19675
+ failedChunks++;
18987
19676
  savePushTs(workspace, sessionId, chunkMaxTs);
18988
19677
  const errBody = await resp.text().catch(() => "?");
18989
19678
  console.warn(`[everos-ingest] memory/add ${resp.status} on chunk ${chunkNo}, body=${errBody.slice(0, 300)}, skip & advance pushTs\u2192${new Date(chunkMaxTs).toISOString().slice(11, 19)}`);
18990
19679
  }
18991
19680
  } catch (e) {
19681
+ failedChunks++;
18992
19682
  savePushTs(workspace, sessionId, chunkMaxTs);
18993
19683
  console.warn(`[everos-ingest] chunk ${chunkNo} push failed (${e?.name || "error"}: ${e?.message || "unknown"}), skip & advance pushTs\u2192${new Date(chunkMaxTs).toISOString().slice(11, 19)}`);
18994
19684
  }
18995
19685
  }
18996
- console.log(`[everos-ingest] done: ${pushed}/${total} msgs pushed`);
19686
+ if (failedChunks === 0) savePushTs(workspace, sessionId, maxTs);
19687
+ console.log(`[everos-ingest] done: ${pushed}/${total} msgs pushed${failedChunks ? ` (${failedChunks} chunk(s) FAILED & skipped \u2014 \u90A3\u4E9B\u6D88\u606F\u4E0D\u4F1A\u91CD\u63A8\uFF0C\u89C1\u51FD\u6570\u5934 \u26A0\uFE0F)` : ""}`);
18997
19688
  } finally {
18998
19689
  pushInProgress = false;
18999
19690
  }
@@ -24488,6 +25179,7 @@ var init_plugin = __esm({
24488
25179
  get name() {
24489
25180
  return current().name ?? "main";
24490
25181
  },
25182
+ // 元组要跟 LLMProvider.formatMessages 的元数对齐(0923 加了第 3 个 opts),否则第 3 参被类型吃掉
24491
25183
  formatMessages: (...args) => current().formatMessages(...args),
24492
25184
  streamChat: (...args) => current().streamChat(args[0]),
24493
25185
  chat: (...args) => {
@@ -25393,11 +26085,11 @@ async function readLastConsolidatedAt(memoryDir) {
25393
26085
  }
25394
26086
  }
25395
26087
  async function tryAcquireConsolidationLock(memoryDir) {
25396
- const path34 = lockPath(memoryDir);
26088
+ const path35 = lockPath(memoryDir);
25397
26089
  let mtimeMs;
25398
26090
  let holderPid;
25399
26091
  try {
25400
- const [s, raw] = await Promise.all([stat3(path34), readFile5(path34, "utf8")]);
26092
+ const [s, raw] = await Promise.all([stat3(path35), readFile5(path35, "utf8")]);
25401
26093
  mtimeMs = s.mtimeMs;
25402
26094
  const parsed = parseInt(raw.trim(), 10);
25403
26095
  holderPid = Number.isFinite(parsed) ? parsed : void 0;
@@ -25410,10 +26102,10 @@ async function tryAcquireConsolidationLock(memoryDir) {
25410
26102
  }
25411
26103
  }
25412
26104
  await mkdir3(memoryDir, { recursive: true });
25413
- await writeFile4(path34, String(process.pid));
26105
+ await writeFile4(path35, String(process.pid));
25414
26106
  let verify;
25415
26107
  try {
25416
- verify = await readFile5(path34, "utf8");
26108
+ verify = await readFile5(path35, "utf8");
25417
26109
  } catch {
25418
26110
  return null;
25419
26111
  }
@@ -25421,15 +26113,15 @@ async function tryAcquireConsolidationLock(memoryDir) {
25421
26113
  return mtimeMs ?? 0;
25422
26114
  }
25423
26115
  async function rollbackConsolidationLock(memoryDir, priorMtime) {
25424
- const path34 = lockPath(memoryDir);
26116
+ const path35 = lockPath(memoryDir);
25425
26117
  try {
25426
26118
  if (priorMtime === 0) {
25427
- await unlink(path34);
26119
+ await unlink(path35);
25428
26120
  return;
25429
26121
  }
25430
- await writeFile4(path34, "");
26122
+ await writeFile4(path35, "");
25431
26123
  const t = priorMtime / 1e3;
25432
- await utimes(path34, t, t);
26124
+ await utimes(path35, t, t);
25433
26125
  } catch (e) {
25434
26126
  console.log(`[autoDream] rollback failed: ${e.message} \u2014 next trigger delayed to minHours`);
25435
26127
  }
@@ -25724,18 +26416,18 @@ function resetSessionStartInjection(sessionId) {
25724
26416
  sessionStartDone.delete(sessionId);
25725
26417
  }
25726
26418
  function loadExternalChanRules(workspace) {
25727
- const path34 = join45(workspace, "prompts", "external-chan-rules.md");
25728
- if (externalChanRulesCache && externalChanRulesCache.path === path34) return externalChanRulesCache;
26419
+ const path35 = join45(workspace, "prompts", "external-chan-rules.md");
26420
+ if (externalChanRulesCache && externalChanRulesCache.path === path35) return externalChanRulesCache;
25729
26421
  let content = "";
25730
- if (existsSync28(path34)) {
26422
+ if (existsSync28(path35)) {
25731
26423
  try {
25732
- content = readFileSync36(path34, "utf-8").trim();
26424
+ content = readFileSync36(path35, "utf-8").trim();
25733
26425
  } catch (e) {
25734
26426
  console.warn(`[external-chan-rules] Failed to load: ${e}`);
25735
26427
  }
25736
26428
  }
25737
- externalChanRulesCache = { path: path34, content };
25738
- console.log(`[external-chan-rules] Loaded ${content.length} chars from ${path34}`);
26429
+ externalChanRulesCache = { path: path35, content };
26430
+ console.log(`[external-chan-rules] Loaded ${content.length} chars from ${path35}`);
25739
26431
  return externalChanRulesCache;
25740
26432
  }
25741
26433
  function getExternalChanRulesBlock(inboundMeta, workspace) {
@@ -26092,6 +26784,8 @@ ${text}` : text });
26092
26784
  let roundThinkingSig = "";
26093
26785
  let roundResponsesReasoning = "";
26094
26786
  let finalResponsesReasoning = "";
26787
+ let finalThinking = "";
26788
+ let finalThinkingSig = "";
26095
26789
  const roundToolCalls = [];
26096
26790
  const roundToolResults = [];
26097
26791
  function flushRound(stopReason) {
@@ -26167,7 +26861,7 @@ ${text}` : text });
26167
26861
  const recallP = deps.recallProvider;
26168
26862
  const recallMode = topics?.recall?.mode || "llm";
26169
26863
  let relevantMemories;
26170
- const effectiveText = textForMemory.replace(/\[图片来自[^\]]*\]|路径:\S+/g, "").trim();
26864
+ const effectiveText = textForMemory.replace(/\[图片来自[^\]]*\]|路径:\S+/g, "").replace(/^\[inner-voice\]\s*/, "").trim();
26171
26865
  if (!effectiveText) {
26172
26866
  console.log("[handle-query] Memory recall skipped: image-only message (no effective text)");
26173
26867
  relevantMemories = [];
@@ -26340,11 +27034,8 @@ ${text}` : text });
26340
27034
  const content = typeof m.content === "string" ? m.content : JSON.stringify(m.content);
26341
27035
  writer.writeUserMessage(content);
26342
27036
  } else if (role === "assistant") {
26343
- const content = typeof m.content === "string" ? m.content : "";
26344
- const toolCalls = m.tool_calls;
26345
27037
  writer.writeAssistantMessage({
26346
- text: content,
26347
- toolCalls: toolCalls?.length > 0 ? toolCalls : void 0,
27038
+ ...assistantWriteParamsFrom(m),
26348
27039
  api: providerApi,
26349
27040
  provider: providerId,
26350
27041
  model
@@ -26386,6 +27077,8 @@ ${text}` : text });
26386
27077
  engine2.clearExternalAbort();
26387
27078
  clearActiveQueryEngine(sessionId);
26388
27079
  finalResponsesReasoning = roundResponsesReasoning;
27080
+ finalThinking = roundThinking;
27081
+ finalThinkingSig = roundThinkingSig;
26389
27082
  if (roundText) {
26390
27083
  try {
26391
27084
  flushRound("endTurn");
@@ -26401,6 +27094,10 @@ ${text}` : text });
26401
27094
  const prePushLen = history.length;
26402
27095
  const finalAssistant = msg.assistant(roundText);
26403
27096
  if (finalResponsesReasoning) finalAssistant.responses_reasoning = finalResponsesReasoning;
27097
+ if (finalThinking) {
27098
+ finalAssistant.reasoning_content = finalThinking;
27099
+ if (finalThinkingSig) finalAssistant.thinking_signature = finalThinkingSig;
27100
+ }
26404
27101
  if (compacted) {
26405
27102
  history.push(...toolHistoryEntries);
26406
27103
  if (roundText) history.push(finalAssistant);
@@ -26571,6 +27268,7 @@ var init_handle_query = __esm({
26571
27268
  init_types();
26572
27269
  init_attachments();
26573
27270
  init_active_query_engine();
27271
+ init_writer();
26574
27272
  init_live();
26575
27273
  init_features();
26576
27274
  init_task_manager();
@@ -26746,8 +27444,8 @@ async function runQuery(args) {
26746
27444
  if (!stateDir) {
26747
27445
  const home = process.env.HOME || process.env.USERPROFILE || ".";
26748
27446
  const cwd = process.cwd();
26749
- const { existsSync: existsSync35 } = await import("node:fs");
26750
- if (existsSync35(join47(cwd, "configs"))) stateDir = cwd;
27447
+ const { existsSync: existsSync36 } = await import("node:fs");
27448
+ if (existsSync36(join47(cwd, "configs"))) stateDir = cwd;
26751
27449
  else {
26752
27450
  try {
26753
27451
  const pointer = JSON.parse(readFileSync37(join47(home, ".engine7.json"), "utf-8"));
@@ -27068,183 +27766,28 @@ var init_cli_ccrun = __esm({
27068
27766
  }
27069
27767
  });
27070
27768
 
27071
- // src/cli-doctor.ts
27072
- var cli_doctor_exports = {};
27073
- __export(cli_doctor_exports, {
27074
- resolveStateDirFromArgs: () => resolveStateDirFromArgs,
27075
- runDoctor: () => runDoctor
27076
- });
27077
- import * as fs28 from "node:fs";
27078
- import * as path30 from "node:path";
27079
- import * as net from "node:net";
27080
- import { fileURLToPath as fileURLToPath5 } from "node:url";
27081
- function readVersion() {
27082
- const here = path30.dirname(fileURLToPath5(import.meta.url));
27083
- for (const p of [path30.join(here, "..", "package.json"), path30.join(here, "..", "..", "package.json")]) {
27084
- try {
27085
- return JSON.parse(fs28.readFileSync(p, "utf8")).version || "unknown";
27086
- } catch {
27087
- }
27088
- }
27089
- return "unknown";
27090
- }
27091
- function channelHasCredentials(name, conf) {
27092
- if (name === "feishu") return !!(conf?.appId && conf?.appSecret);
27093
- if (name === "wechat") return !!(conf?.token && conf?.accountId);
27094
- if (name === "discord") return !!conf?.accounts?.default?.token;
27095
- return true;
27096
- }
27097
- function probePort(port, timeoutMs) {
27098
- return new Promise((resolve12) => {
27099
- const sock = net.connect({ port, host: "127.0.0.1", timeout: timeoutMs });
27100
- sock.once("connect", () => {
27101
- sock.destroy();
27102
- resolve12(true);
27103
- });
27104
- sock.once("error", () => resolve12(false));
27105
- sock.once("timeout", () => {
27106
- sock.destroy();
27107
- resolve12(false);
27108
- });
27109
- });
27110
- }
27111
- async function runDoctor(stateDir, opts) {
27112
- const home = process.env.HOME || process.env.USERPROFILE || ".";
27113
- const configPath = path30.join(stateDir, "configs", "main7.json");
27114
- const report = {
27115
- version: readVersion(),
27116
- stateDir,
27117
- configPath,
27118
- configValid: false,
27119
- channels: [],
27120
- secrets: { file: "", exists: false, keys: [] },
27121
- apiPort: 16890,
27122
- engineAlive: false,
27123
- vision: { ref: null, providerExists: null },
27124
- missingEnvKeys: []
27125
- };
27126
- let config = null;
27127
- try {
27128
- config = JSON.parse(fs28.readFileSync(configPath, "utf8"));
27129
- report.configValid = true;
27130
- } catch (e) {
27131
- report.configError = fs28.existsSync(configPath) ? `\u89E3\u6790\u5931\u8D25: ${e.message}` : "config \u4E0D\u5B58\u5728\uFF08\u5148 engine7 init\uFF09";
27132
- }
27133
- if (config?.channels) {
27134
- for (const [name, conf] of Object.entries(config.channels)) {
27135
- const c = conf;
27136
- report.channels.push({ name, enabled: !!c.enabled, hasCredentials: channelHasCredentials(name, c) });
27137
- }
27138
- }
27139
- const secretsDir = path30.join(home, ".engine7-secrets");
27140
- const cfgBase = path30.basename(report.configPath, ".json") || "main7";
27141
- const secretsCandidates = [
27142
- path30.join(secretsDir, `${cfgBase}.env`),
27143
- path30.join(stateDir, "configs", `.env.${cfgBase}`),
27144
- path30.join(stateDir, "configs", ".env")
27145
- ];
27146
- const secretsFile = secretsCandidates.find((p) => fs28.existsSync(p)) || secretsCandidates[0];
27147
- report.secrets.file = secretsFile;
27148
- if (fs28.existsSync(secretsFile)) {
27149
- report.secrets.exists = true;
27150
- for (const line of fs28.readFileSync(secretsFile, "utf8").split("\n")) {
27151
- const eq = line.indexOf("=");
27152
- if (eq > 0 && !line.startsWith("#")) report.secrets.keys.push(line.slice(0, eq).trim());
27153
- }
27154
- }
27155
- const visionRef = config?.agents?.defaults?.model?.vision;
27156
- if (typeof visionRef === "string" && visionRef) {
27157
- const providerId = visionRef.includes("/") ? visionRef.slice(0, visionRef.indexOf("/")) : "";
27158
- report.vision = {
27159
- ref: visionRef,
27160
- providerExists: !!providerId && !!config?.models?.providers?.[providerId]
27161
- };
27162
- }
27163
- const missing = /* @__PURE__ */ new Set();
27164
- const walk = (node) => {
27165
- if (typeof node === "string") {
27166
- if (node.startsWith("env:")) {
27167
- const name = node.slice(4);
27168
- if (/^[A-Z][A-Z0-9_]*$/.test(name) && process.env[name] === void 0 && !report.secrets.keys.includes(name)) {
27169
- missing.add(name);
27170
- }
27171
- }
27172
- return;
27173
- }
27174
- if (Array.isArray(node)) {
27175
- node.forEach(walk);
27176
- return;
27177
- }
27178
- if (node && typeof node === "object") {
27179
- for (const k of Object.keys(node)) walk(node[k]);
27180
- }
27181
- };
27182
- walk(config);
27183
- report.missingEnvKeys = [...missing].sort();
27184
- const stale = [];
27185
- for (const rel of ["workspace/SESSION-STATE.md", "SESSION-STATE.md"]) {
27186
- const p = path30.join(stateDir, rel);
27187
- try {
27188
- if (!fs28.existsSync(p)) continue;
27189
- const head = fs28.readFileSync(p, "utf8").split("\n").slice(0, 10).join("\n");
27190
- const m = head.match(/^#{1,3}\s*当前时间\s*$\n+\s*(\d{4}-\d{2}-\d{2})/m);
27191
- if (!m) continue;
27192
- const ageH = (Date.now() - (/* @__PURE__ */ new Date(`${m[1]}T00:00:00Z`)).getTime()) / 36e5;
27193
- if (ageH > 48) stale.push(`${rel}: ${m[1]}`);
27194
- } catch {
27195
- }
27196
- }
27197
- report.staleTimeHeaders = stale;
27198
- report.apiPort = config?.api?.port || 16890;
27199
- report.engineAlive = await probePort(report.apiPort, opts?.probeTimeoutMs ?? 1500);
27200
- return report;
27201
- }
27202
- function resolveStateDirFromArgs(args) {
27203
- let stateDir = "";
27204
- for (let i = 0; i < args.length; i++) {
27205
- if (args[i] === "--state-dir" && args[i + 1]) stateDir = args[++i].trim();
27206
- }
27207
- if (stateDir) return path30.resolve(stateDir);
27208
- const home = process.env.HOME || process.env.USERPROFILE || ".";
27209
- const cwd = path30.resolve(process.cwd());
27210
- if (fs28.existsSync(path30.join(cwd, "configs"))) return cwd;
27211
- const pointer = path30.join(home, ".engine7.json");
27212
- if (fs28.existsSync(pointer)) {
27213
- try {
27214
- const ptrStateDir = JSON.parse(fs28.readFileSync(pointer, "utf8")).stateDir;
27215
- if (typeof ptrStateDir === "string" && ptrStateDir.trim()) return path30.resolve(ptrStateDir);
27216
- } catch {
27217
- }
27218
- }
27219
- return path30.join(home, ".engine7");
27220
- }
27221
- var init_cli_doctor = __esm({
27222
- "src/cli-doctor.ts"() {
27223
- "use strict";
27224
- }
27225
- });
27226
-
27227
27769
  // src/provision-free.ts
27228
27770
  var provision_free_exports = {};
27229
27771
  __export(provision_free_exports, {
27772
+ FREE_TIER_ENV_KEY: () => FREE_TIER_ENV_KEY,
27230
27773
  loadOrCreateInstallIdentity: () => loadOrCreateInstallIdentity,
27231
27774
  provisionFreeToken: () => provisionFreeToken,
27232
27775
  readInstallIdentity: () => readInstallIdentity,
27233
27776
  writeIdentityContactEmail: () => writeIdentityContactEmail
27234
27777
  });
27235
27778
  import { randomBytes as randomBytes6, randomUUID as randomUUID9 } from "node:crypto";
27236
- import { chmodSync as chmodSync2, existsSync as existsSync31, mkdirSync as mkdirSync19, readFileSync as readFileSync39, writeFileSync as writeFileSync20 } from "node:fs";
27237
- import { dirname as dirname12, join as join49 } from "node:path";
27238
- import { fileURLToPath as fileURLToPath6 } from "node:url";
27779
+ import { chmodSync as chmodSync2, existsSync as existsSync30, mkdirSync as mkdirSync19, readFileSync as readFileSync38, writeFileSync as writeFileSync20 } from "node:fs";
27780
+ import { dirname as dirname11, join as join48 } from "node:path";
27781
+ import { fileURLToPath as fileURLToPath5 } from "node:url";
27239
27782
  function readContactEmailOf(parsed) {
27240
27783
  return typeof parsed?.contactEmail === "string" ? parsed.contactEmail.trim() : "";
27241
27784
  }
27242
27785
  function loadOrCreateInstallIdentity(stateDir) {
27243
- const file2 = join49(stateDir, IDENTITY_FILE);
27244
- if (existsSync31(file2)) {
27786
+ const file2 = join48(stateDir, IDENTITY_FILE);
27787
+ if (existsSync30(file2)) {
27245
27788
  let parsed;
27246
27789
  try {
27247
- parsed = JSON.parse(readFileSync39(file2, "utf8"));
27790
+ parsed = JSON.parse(readFileSync38(file2, "utf8"));
27248
27791
  } catch (e) {
27249
27792
  throw new Error(
27250
27793
  `${IDENTITY_FILE} \u5B58\u5728\u4F46\u4E0D\u662F\u5408\u6CD5 JSON\uFF08${e.message}\uFF09\u2014\u2014\u5220\u6389\u5B83\u91CD\u8DD1\u4F1A\u9886\u4E00\u628A\u65B0\u4EE4\u724C\uFF0C\u65E7\u7684\u90A3\u628A\u53D6\u4E0D\u56DE\u6765`
@@ -27276,7 +27819,7 @@ function loadOrCreateInstallIdentity(stateDir) {
27276
27819
  }
27277
27820
  function readInstallIdentity(stateDir) {
27278
27821
  try {
27279
- const parsed = JSON.parse(readFileSync39(join49(stateDir, IDENTITY_FILE), "utf8"));
27822
+ const parsed = JSON.parse(readFileSync38(join48(stateDir, IDENTITY_FILE), "utf8"));
27280
27823
  if (typeof parsed?.installId !== "string" || typeof parsed?.installSecret !== "string") return null;
27281
27824
  const email = readContactEmailOf(parsed);
27282
27825
  return {
@@ -27292,7 +27835,7 @@ function writeIdentityContactEmail(stateDir, email) {
27292
27835
  const identity = loadOrCreateInstallIdentity(stateDir);
27293
27836
  const trimmed = email.trim();
27294
27837
  const next = { ...identity, contactEmail: trimmed };
27295
- const file2 = join49(stateDir, IDENTITY_FILE);
27838
+ const file2 = join48(stateDir, IDENTITY_FILE);
27296
27839
  mkdirSync19(stateDir, { recursive: true });
27297
27840
  writeFileSync20(file2, JSON.stringify(next, null, 2) + "\n", { mode: 384 });
27298
27841
  try {
@@ -27302,7 +27845,7 @@ function writeIdentityContactEmail(stateDir, email) {
27302
27845
  }
27303
27846
  function readEngineVersion() {
27304
27847
  try {
27305
- return JSON.parse(readFileSync39(join49(dirname12(__filename), "..", "package.json"), "utf8")).version || "unknown";
27848
+ return JSON.parse(readFileSync38(join48(dirname11(__filename), "..", "package.json"), "utf8")).version || "unknown";
27306
27849
  } catch {
27307
27850
  return "unknown";
27308
27851
  }
@@ -27350,76 +27893,335 @@ var init_provision_free = __esm({
27350
27893
  "src/provision-free.ts"() {
27351
27894
  "use strict";
27352
27895
  init_secrets_env();
27353
- __filename = fileURLToPath6(import.meta.url);
27896
+ __filename = fileURLToPath5(import.meta.url);
27354
27897
  IDENTITY_FILE = "install-identity.json";
27355
27898
  FREE_TIER_ENV_KEY = "7YI_API_KEY";
27356
27899
  }
27357
27900
  });
27358
27901
 
27359
- // src/cli-reconfig.ts
27360
- var cli_reconfig_exports = {};
27361
- __export(cli_reconfig_exports, {
27362
- doReconfigFeishu: () => doReconfigFeishu,
27363
- doReconfigWechat: () => doReconfigWechat
27902
+ // src/free-tier-apply.ts
27903
+ var free_tier_apply_exports = {};
27904
+ __export(free_tier_apply_exports, {
27905
+ FREE_TIER_PRIMARY: () => FREE_TIER_PRIMARY,
27906
+ FREE_TIER_PROVIDER_ID: () => FREE_TIER_PROVIDER_ID,
27907
+ applyFreeTier: () => applyFreeTier,
27908
+ applyFreeTierToConfig: () => applyFreeTierToConfig,
27909
+ isFreeTierConfig: () => isFreeTierConfig,
27910
+ resolveConfigPath: () => resolveConfigPath
27364
27911
  });
27365
- import * as fs29 from "node:fs";
27366
- import * as path31 from "node:path";
27367
- function scanForChannelsConfig(configsDir) {
27368
- if (!fs29.existsSync(configsDir)) return "";
27369
- for (const f of fs29.readdirSync(configsDir).filter((x) => x.endsWith(".json"))) {
27370
- const full = path31.join(configsDir, f);
27912
+ import * as fs28 from "node:fs";
27913
+ import * as path30 from "node:path";
27914
+ function isFreeTierConfig(cfg) {
27915
+ const hasProvider = !!cfg?.models?.providers?.[FREE_TIER_PROVIDER_ID];
27916
+ const primary = String(cfg?.agents?.defaults?.model?.primary || "");
27917
+ return hasProvider && primary.startsWith(`${FREE_TIER_PROVIDER_ID}/`);
27918
+ }
27919
+ function applyFreeTier(cfg, opts = {}) {
27920
+ const def = PROVIDER_DEFS[FREE_TIER_PROVIDER_ID];
27921
+ if (!def) throw new Error("\u5F15\u64CE\u91CC\u6CA1\u6709 7yi \u8FD9\u4E2A\u5185\u7F6E provider\u2014\u2014PROVIDER_DEFS \u88AB\u52A8\u8FC7\uFF1F");
27922
+ const baseUrl = (opts.baseUrl || "").trim() || def.baseUrl;
27923
+ const vision = resolveVisionModel(FREE_TIER_PROVIDER_ID, FREE_TIER_PRIMARY);
27924
+ cfg.models = cfg.models || {};
27925
+ cfg.models.providers = cfg.models.providers || {};
27926
+ cfg.models.providers[FREE_TIER_PROVIDER_ID] = {
27927
+ ...def,
27928
+ // 深拷一份模型表:`...def` 是浅拷,别让写出来的 config 和 PROVIDER_DEFS 共享同一个数组
27929
+ models: (def.models || []).map((m) => ({ ...m })),
27930
+ apiKey: `env:${FREE_TIER_ENV_KEY}`,
27931
+ ...baseUrl ? { baseUrl: String(baseUrl).trim() } : {}
27932
+ };
27933
+ cfg.agents = cfg.agents || {};
27934
+ cfg.agents.defaults = cfg.agents.defaults || {};
27935
+ cfg.agents.defaults.model = cfg.agents.defaults.model || {};
27936
+ cfg.agents.defaults.model.primary = FREE_TIER_PRIMARY;
27937
+ if (vision) cfg.agents.defaults.model.vision = vision;
27938
+ cfg.agents.defaults.features = cfg.agents.defaults.features || {};
27939
+ cfg.agents.defaults.features.selfie = true;
27940
+ cfg.agents.defaults.features.eyes = true;
27941
+ cfg.tools = cfg.tools || {};
27942
+ if (vision) cfg.tools.my_eyes = { ...cfg.tools.my_eyes || {}, model: vision };
27943
+ cfg.tools.my_selfie = { ...cfg.tools.my_selfie || {}, provider: "relay" };
27944
+ return cfg;
27945
+ }
27946
+ function resolveConfigPath(stateDir, configName) {
27947
+ const configsDir = path30.join(stateDir, "configs");
27948
+ if (configName) {
27949
+ const p = path30.join(configsDir, configName);
27950
+ return fs28.existsSync(p) ? p : "";
27951
+ }
27952
+ const main2 = path30.join(configsDir, "main7.json");
27953
+ if (fs28.existsSync(main2)) return main2;
27954
+ if (!fs28.existsSync(configsDir)) return "";
27955
+ for (const f of fs28.readdirSync(configsDir).filter((x) => x.endsWith(".json"))) {
27956
+ const full = path30.join(configsDir, f);
27371
27957
  try {
27372
- if (JSON.parse(fs29.readFileSync(full, "utf8")).channels) return full;
27958
+ if (JSON.parse(fs28.readFileSync(full, "utf8")).models?.providers) return full;
27373
27959
  } catch {
27374
27960
  }
27375
27961
  }
27376
27962
  return "";
27377
27963
  }
27378
- function findFeishuConfigFile(stateDir, configName) {
27379
- const configsDir = path31.join(stateDir, "configs");
27380
- if (configName) {
27381
- const p = path31.join(configsDir, configName);
27382
- if (fs29.existsSync(p)) return p;
27964
+ function applyFreeTierToConfig(configPath, opts = {}) {
27965
+ let cfg;
27966
+ try {
27967
+ cfg = JSON.parse(fs28.readFileSync(configPath, "utf8"));
27968
+ } catch (e) {
27969
+ throw new Error(`\u8BFB\u53D6 config \u5931\u8D25: ${configPath}\uFF08${e?.message || e}\uFF09`);
27383
27970
  }
27384
- return scanForChannelsConfig(configsDir);
27971
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
27972
+ const backupPath = `${configPath}.bak-${stamp}`;
27973
+ fs28.copyFileSync(configPath, backupPath);
27974
+ applyFreeTier(cfg, opts);
27975
+ try {
27976
+ fs28.writeFileSync(configPath, JSON.stringify(cfg, null, 2), "utf8");
27977
+ } catch (e) {
27978
+ throw new Error(`\u5199\u5165 config \u5931\u8D25: ${configPath}\uFF08${e?.message || e}\uFF09\uFF1B\u539F\u6587\u4EF6\u672A\u6539\u52A8\uFF0C\u5907\u4EFD\u5728 ${backupPath}`);
27979
+ }
27980
+ return {
27981
+ configPath,
27982
+ backupPath,
27983
+ primary: cfg.agents.defaults.model.primary,
27984
+ vision: cfg.agents.defaults.model.vision ?? null
27985
+ };
27385
27986
  }
27386
- function findWechatConfigFile(stateDir, configName) {
27387
- const configsDir = path31.join(stateDir, "configs");
27388
- if (configName) {
27389
- const p = path31.join(configsDir, configName);
27390
- return fs29.existsSync(p) ? p : "";
27987
+ var FREE_TIER_PROVIDER_ID, FREE_TIER_PRIMARY;
27988
+ var init_free_tier_apply = __esm({
27989
+ "src/free-tier-apply.ts"() {
27990
+ "use strict";
27991
+ init_cli_init_config();
27992
+ init_provision_free();
27993
+ FREE_TIER_PROVIDER_ID = "7yi";
27994
+ FREE_TIER_PRIMARY = "7yi/engine7-free";
27391
27995
  }
27392
- const scanned = scanForChannelsConfig(configsDir);
27393
- if (scanned) return scanned;
27394
- if (fs29.existsSync(configsDir)) {
27395
- const candidates = fs29.readdirSync(configsDir).filter((f) => f.endsWith(".json"));
27396
- if (candidates.length) {
27397
- const first = path31.join(configsDir, candidates[0]);
27398
- try {
27399
- JSON.parse(fs29.readFileSync(first, "utf8"));
27400
- return first;
27401
- } catch {
27402
- }
27996
+ });
27997
+
27998
+ // src/cli-doctor.ts
27999
+ var cli_doctor_exports = {};
28000
+ __export(cli_doctor_exports, {
28001
+ resolveStateDirFromArgs: () => resolveStateDirFromArgs,
28002
+ runDoctor: () => runDoctor
28003
+ });
28004
+ import * as fs29 from "node:fs";
28005
+ import * as path31 from "node:path";
28006
+ import * as net from "node:net";
28007
+ import { fileURLToPath as fileURLToPath6 } from "node:url";
28008
+ function readVersion() {
28009
+ const here = path31.dirname(fileURLToPath6(import.meta.url));
28010
+ for (const p of [path31.join(here, "..", "package.json"), path31.join(here, "..", "..", "package.json")]) {
28011
+ try {
28012
+ return JSON.parse(fs29.readFileSync(p, "utf8")).version || "unknown";
28013
+ } catch {
27403
28014
  }
27404
28015
  }
27405
- return "";
28016
+ return "unknown";
27406
28017
  }
27407
- async function doReconfigFeishu(opts) {
27408
- const { stateDir, configName, events, emit: emit2, deps } = opts;
27409
- const log = (msg2) => console.log(msg2);
27410
- if (!events) {
27411
- log("\u{1F527} \u98DE\u4E66\u626B\u7801\u63A5\u5165...");
27412
- }
27413
- const result = await (deps?.feishuRegister || registerFeishuApp)({
27414
- onLog: (msg2) => {
27415
- if (!events) console.log(` ${msg2}`);
27416
- },
27417
- onQrCode: async (url) => {
27418
- if (events) {
27419
- emit2?.({ event: "qr", url });
27420
- return;
27421
- }
27422
- try {
28018
+ function channelHasCredentials(name, conf) {
28019
+ if (name === "feishu") return !!(conf?.appId && conf?.appSecret);
28020
+ if (name === "wechat") return !!(conf?.token && conf?.accountId);
28021
+ if (name === "discord") return !!conf?.accounts?.default?.token;
28022
+ return true;
28023
+ }
28024
+ function probePort(port, timeoutMs) {
28025
+ return new Promise((resolve12) => {
28026
+ const sock = net.connect({ port, host: "127.0.0.1", timeout: timeoutMs });
28027
+ sock.once("connect", () => {
28028
+ sock.destroy();
28029
+ resolve12(true);
28030
+ });
28031
+ sock.once("error", () => resolve12(false));
28032
+ sock.once("timeout", () => {
28033
+ sock.destroy();
28034
+ resolve12(false);
28035
+ });
28036
+ });
28037
+ }
28038
+ async function runDoctor(stateDir, opts) {
28039
+ const home = process.env.HOME || process.env.USERPROFILE || ".";
28040
+ const configPath = path31.join(stateDir, "configs", "main7.json");
28041
+ const report = {
28042
+ version: readVersion(),
28043
+ stateDir,
28044
+ configPath,
28045
+ configValid: false,
28046
+ channels: [],
28047
+ secrets: { file: "", exists: false, keys: [] },
28048
+ apiPort: 16890,
28049
+ engineAlive: false,
28050
+ vision: { ref: null, providerExists: null },
28051
+ missingEnvKeys: [],
28052
+ freeTier: { on: false, tokenPresent: false }
28053
+ };
28054
+ let config = null;
28055
+ try {
28056
+ config = JSON.parse(fs29.readFileSync(configPath, "utf8"));
28057
+ report.configValid = true;
28058
+ } catch (e) {
28059
+ report.configError = fs29.existsSync(configPath) ? `\u89E3\u6790\u5931\u8D25: ${e.message}` : "config \u4E0D\u5B58\u5728\uFF08\u5148 engine7 init\uFF09";
28060
+ }
28061
+ if (config?.channels) {
28062
+ for (const [name, conf] of Object.entries(config.channels)) {
28063
+ const c = conf;
28064
+ report.channels.push({ name, enabled: !!c.enabled, hasCredentials: channelHasCredentials(name, c) });
28065
+ }
28066
+ }
28067
+ const secretsDir = path31.join(home, ".engine7-secrets");
28068
+ const cfgBase = path31.basename(report.configPath, ".json") || "main7";
28069
+ const secretsCandidates = [
28070
+ path31.join(secretsDir, `${cfgBase}.env`),
28071
+ path31.join(stateDir, "configs", `.env.${cfgBase}`),
28072
+ path31.join(stateDir, "configs", ".env")
28073
+ ];
28074
+ const secretsFile = secretsCandidates.find((p) => fs29.existsSync(p)) || secretsCandidates[0];
28075
+ report.secrets.file = secretsFile;
28076
+ if (fs29.existsSync(secretsFile)) {
28077
+ report.secrets.exists = true;
28078
+ for (const line of fs29.readFileSync(secretsFile, "utf8").split("\n")) {
28079
+ const eq = line.indexOf("=");
28080
+ if (eq > 0 && !line.startsWith("#")) report.secrets.keys.push(line.slice(0, eq).trim());
28081
+ }
28082
+ }
28083
+ report.freeTier = {
28084
+ on: isFreeTierConfig(config),
28085
+ tokenPresent: report.secrets.keys.includes(FREE_TIER_ENV_KEY)
28086
+ };
28087
+ const visionRef = config?.agents?.defaults?.model?.vision;
28088
+ if (typeof visionRef === "string" && visionRef) {
28089
+ const providerId = visionRef.includes("/") ? visionRef.slice(0, visionRef.indexOf("/")) : "";
28090
+ report.vision = {
28091
+ ref: visionRef,
28092
+ providerExists: !!providerId && !!config?.models?.providers?.[providerId]
28093
+ };
28094
+ }
28095
+ const missing = /* @__PURE__ */ new Set();
28096
+ const walk = (node) => {
28097
+ if (typeof node === "string") {
28098
+ if (node.startsWith("env:")) {
28099
+ const name = node.slice(4);
28100
+ if (/^[A-Z][A-Z0-9_]*$/.test(name) && process.env[name] === void 0 && !report.secrets.keys.includes(name)) {
28101
+ missing.add(name);
28102
+ }
28103
+ }
28104
+ return;
28105
+ }
28106
+ if (Array.isArray(node)) {
28107
+ node.forEach(walk);
28108
+ return;
28109
+ }
28110
+ if (node && typeof node === "object") {
28111
+ for (const k of Object.keys(node)) walk(node[k]);
28112
+ }
28113
+ };
28114
+ walk(config);
28115
+ report.missingEnvKeys = [...missing].sort();
28116
+ const stale = [];
28117
+ for (const rel of ["workspace/SESSION-STATE.md", "SESSION-STATE.md"]) {
28118
+ const p = path31.join(stateDir, rel);
28119
+ try {
28120
+ if (!fs29.existsSync(p)) continue;
28121
+ const head = fs29.readFileSync(p, "utf8").split("\n").slice(0, 10).join("\n");
28122
+ const m = head.match(/^#{1,3}\s*当前时间\s*$\n+\s*(\d{4}-\d{2}-\d{2})/m);
28123
+ if (!m) continue;
28124
+ const ageH = (Date.now() - (/* @__PURE__ */ new Date(`${m[1]}T00:00:00Z`)).getTime()) / 36e5;
28125
+ if (ageH > 48) stale.push(`${rel}: ${m[1]}`);
28126
+ } catch {
28127
+ }
28128
+ }
28129
+ report.staleTimeHeaders = stale;
28130
+ report.apiPort = config?.api?.port || 16890;
28131
+ report.engineAlive = await probePort(report.apiPort, opts?.probeTimeoutMs ?? 1500);
28132
+ return report;
28133
+ }
28134
+ function resolveStateDirFromArgs(args) {
28135
+ let stateDir = "";
28136
+ for (let i = 0; i < args.length; i++) {
28137
+ if (args[i] === "--state-dir" && args[i + 1]) stateDir = args[++i].trim();
28138
+ }
28139
+ if (stateDir) return path31.resolve(stateDir);
28140
+ const home = process.env.HOME || process.env.USERPROFILE || ".";
28141
+ const cwd = path31.resolve(process.cwd());
28142
+ if (fs29.existsSync(path31.join(cwd, "configs"))) return cwd;
28143
+ const pointer = path31.join(home, ".engine7.json");
28144
+ if (fs29.existsSync(pointer)) {
28145
+ try {
28146
+ const ptrStateDir = JSON.parse(fs29.readFileSync(pointer, "utf8")).stateDir;
28147
+ if (typeof ptrStateDir === "string" && ptrStateDir.trim()) return path31.resolve(ptrStateDir);
28148
+ } catch {
28149
+ }
28150
+ }
28151
+ return path31.join(home, ".engine7");
28152
+ }
28153
+ var init_cli_doctor = __esm({
28154
+ "src/cli-doctor.ts"() {
28155
+ "use strict";
28156
+ init_free_tier_apply();
28157
+ init_provision_free();
28158
+ }
28159
+ });
28160
+
28161
+ // src/cli-reconfig.ts
28162
+ var cli_reconfig_exports = {};
28163
+ __export(cli_reconfig_exports, {
28164
+ doReconfigFeishu: () => doReconfigFeishu,
28165
+ doReconfigWechat: () => doReconfigWechat
28166
+ });
28167
+ import * as fs30 from "node:fs";
28168
+ import * as path32 from "node:path";
28169
+ function scanForChannelsConfig(configsDir) {
28170
+ if (!fs30.existsSync(configsDir)) return "";
28171
+ for (const f of fs30.readdirSync(configsDir).filter((x) => x.endsWith(".json"))) {
28172
+ const full = path32.join(configsDir, f);
28173
+ try {
28174
+ if (JSON.parse(fs30.readFileSync(full, "utf8")).channels) return full;
28175
+ } catch {
28176
+ }
28177
+ }
28178
+ return "";
28179
+ }
28180
+ function findFeishuConfigFile(stateDir, configName) {
28181
+ const configsDir = path32.join(stateDir, "configs");
28182
+ if (configName) {
28183
+ const p = path32.join(configsDir, configName);
28184
+ if (fs30.existsSync(p)) return p;
28185
+ }
28186
+ return scanForChannelsConfig(configsDir);
28187
+ }
28188
+ function findWechatConfigFile(stateDir, configName) {
28189
+ const configsDir = path32.join(stateDir, "configs");
28190
+ if (configName) {
28191
+ const p = path32.join(configsDir, configName);
28192
+ return fs30.existsSync(p) ? p : "";
28193
+ }
28194
+ const scanned = scanForChannelsConfig(configsDir);
28195
+ if (scanned) return scanned;
28196
+ if (fs30.existsSync(configsDir)) {
28197
+ const candidates = fs30.readdirSync(configsDir).filter((f) => f.endsWith(".json"));
28198
+ if (candidates.length) {
28199
+ const first = path32.join(configsDir, candidates[0]);
28200
+ try {
28201
+ JSON.parse(fs30.readFileSync(first, "utf8"));
28202
+ return first;
28203
+ } catch {
28204
+ }
28205
+ }
28206
+ }
28207
+ return "";
28208
+ }
28209
+ async function doReconfigFeishu(opts) {
28210
+ const { stateDir, configName, events, emit: emit2, deps } = opts;
28211
+ const log = (msg2) => console.log(msg2);
28212
+ if (!events) {
28213
+ log("\u{1F527} \u98DE\u4E66\u626B\u7801\u63A5\u5165...");
28214
+ }
28215
+ const result = await (deps?.feishuRegister || registerFeishuApp)({
28216
+ onLog: (msg2) => {
28217
+ if (!events) console.log(` ${msg2}`);
28218
+ },
28219
+ onQrCode: async (url) => {
28220
+ if (events) {
28221
+ emit2?.({ event: "qr", url });
28222
+ return;
28223
+ }
28224
+ try {
27423
28225
  const { renderQrTerminal: renderQrTerminal2 } = await Promise.resolve().then(() => (init_qr_render(), qr_render_exports));
27424
28226
  console.log(await renderQrTerminal2(url, { small: true }));
27425
28227
  } catch {
@@ -27435,11 +28237,11 @@ async function doReconfigFeishu(opts) {
27435
28237
  return 2;
27436
28238
  }
27437
28239
  const configFile = findFeishuConfigFile(stateDir, configName);
27438
- if (configFile && fs29.existsSync(configFile)) {
28240
+ if (configFile && fs30.existsSync(configFile)) {
27439
28241
  if (events) emit2?.({ event: "progress", step: "write_config" });
27440
28242
  let j;
27441
28243
  try {
27442
- j = JSON.parse(fs29.readFileSync(configFile, "utf8"));
28244
+ j = JSON.parse(fs30.readFileSync(configFile, "utf8"));
27443
28245
  } catch (err) {
27444
28246
  if (events) emit2?.({ event: "error", message: `\u8BFB\u53D6 config \u5931\u8D25: ${configFile}\uFF08${err?.message || err}\uFF09` });
27445
28247
  else console.error(`\u274C \u8BFB\u53D6 config \u5931\u8D25: ${configFile}\uFF08${err?.message || err}\uFF09`);
@@ -27448,7 +28250,7 @@ async function doReconfigFeishu(opts) {
27448
28250
  j.channels = j.channels || {};
27449
28251
  j.channels.feishu = { enabled: true, appId: result.appId, appSecret: result.appSecret, connectionMode: "websocket", dmPolicy: "pairing", groupPolicy: "open" };
27450
28252
  try {
27451
- fs29.writeFileSync(configFile, JSON.stringify(j, null, 2), "utf8");
28253
+ fs30.writeFileSync(configFile, JSON.stringify(j, null, 2), "utf8");
27452
28254
  } catch (err) {
27453
28255
  if (events) emit2?.({ event: "error", message: `\u5199\u5165 config \u5931\u8D25: ${configFile}\uFF08${err?.message || err}\uFF09` });
27454
28256
  else console.error(`\u274C \u5199\u5165 config \u5931\u8D25: ${configFile}\uFF08${err?.message || err}\uFF09`);
@@ -27481,7 +28283,7 @@ async function doReconfigWechat(opts) {
27481
28283
  return 2;
27482
28284
  }
27483
28285
  const configFile = findWechatConfigFile(stateDir, configName);
27484
- if (!configFile || !fs29.existsSync(configFile)) {
28286
+ if (!configFile || !fs30.existsSync(configFile)) {
27485
28287
  console.log("\n\u26A0\uFE0F \u6CA1\u627E\u5230 config \u6587\u4EF6\uFF0C\u8BF7\u624B\u52A8\u628A\u4EE5\u4E0B\u6BB5\u52A0\u8FDB\u4F60\u7684 config \u7684 channels \u91CC\uFF1A");
27486
28288
  console.log(JSON.stringify({ wechat: { enabled: true, token: cred.token, accountId: cred.accountId, dmPolicy: "pairing", group: { policy: "disabled" } } }, null, 2));
27487
28289
  if (events) emit2?.({ event: "done", channel: "wechat", detail: cred.accountId, restart_required: true });
@@ -27490,7 +28292,7 @@ async function doReconfigWechat(opts) {
27490
28292
  if (events) emit2?.({ event: "progress", step: "write_config" });
27491
28293
  let j;
27492
28294
  try {
27493
- j = JSON.parse(fs29.readFileSync(configFile, "utf8"));
28295
+ j = JSON.parse(fs30.readFileSync(configFile, "utf8"));
27494
28296
  } catch (err) {
27495
28297
  if (events) emit2?.({ event: "error", message: `\u8BFB\u53D6 config \u5931\u8D25: ${configFile}\uFF08${err?.message || err}\uFF09` });
27496
28298
  else console.error(`\u274C \u8BFB\u53D6 config \u5931\u8D25: ${configFile}\uFF08${err?.message || err}\uFF09`);
@@ -27499,7 +28301,7 @@ async function doReconfigWechat(opts) {
27499
28301
  j.channels = j.channels || {};
27500
28302
  j.channels.wechat = { enabled: true, token: cred.token, accountId: cred.accountId, dmPolicy: "pairing", group: { policy: "disabled" } };
27501
28303
  try {
27502
- fs29.writeFileSync(configFile, JSON.stringify(j, null, 2), "utf8");
28304
+ fs30.writeFileSync(configFile, JSON.stringify(j, null, 2), "utf8");
27503
28305
  } catch (err) {
27504
28306
  if (events) emit2?.({ event: "error", message: `\u5199\u5165 config \u5931\u8D25: ${configFile}\uFF08${err?.message || err}\uFF09` });
27505
28307
  else console.error(`\u274C \u5199\u5165 config \u5931\u8D25: ${configFile}\uFF08${err?.message || err}\uFF09`);
@@ -27534,20 +28336,20 @@ __export(cli_travel_exports, {
27534
28336
  resolveArchiveForTest: () => resolveArchiveForTest,
27535
28337
  saveTravelConfig: () => saveTravelConfig
27536
28338
  });
27537
- import * as path32 from "node:path";
27538
- import * as fs30 from "node:fs";
28339
+ import * as path33 from "node:path";
28340
+ import * as fs31 from "node:fs";
27539
28341
  import * as os5 from "node:os";
27540
28342
  import { execSync as execSync2 } from "node:child_process";
27541
28343
  function loadTravelConfig() {
27542
28344
  try {
27543
- if (!fs30.existsSync(CONFIG_PATH)) return null;
27544
- return JSON.parse(fs30.readFileSync(CONFIG_PATH, "utf-8"));
28345
+ if (!fs31.existsSync(CONFIG_PATH)) return null;
28346
+ return JSON.parse(fs31.readFileSync(CONFIG_PATH, "utf-8"));
27545
28347
  } catch {
27546
28348
  return null;
27547
28349
  }
27548
28350
  }
27549
28351
  function saveTravelConfig(cfg) {
27550
- fs30.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2));
28352
+ fs31.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2));
27551
28353
  }
27552
28354
  function sanitizeContent(content, dirs) {
27553
28355
  let result = content;
@@ -27572,7 +28374,7 @@ function restoreContent(content, dirs) {
27572
28374
  return result;
27573
28375
  }
27574
28376
  function isTextFile(filePath) {
27575
- const ext = path32.extname(filePath).toLowerCase();
28377
+ const ext = path33.extname(filePath).toLowerCase();
27576
28378
  return TEXT_EXTENSIONS.has(ext);
27577
28379
  }
27578
28380
  function shouldExclude(name) {
@@ -27587,14 +28389,14 @@ function shouldExclude(name) {
27587
28389
  }
27588
28390
  function collectFiles(rootDir, includeSet, optionalSet) {
27589
28391
  const files = [];
27590
- const entries2 = fs30.readdirSync(rootDir, { withFileTypes: true });
28392
+ const entries2 = fs31.readdirSync(rootDir, { withFileTypes: true });
27591
28393
  for (const entry of entries2) {
27592
28394
  if (shouldExclude(entry.name)) continue;
27593
- const fullPath = path32.join(rootDir, entry.name);
27594
- const realPath = fs30.realpathSync(fullPath);
28395
+ const fullPath = path33.join(rootDir, entry.name);
28396
+ const realPath = fs31.realpathSync(fullPath);
27595
28397
  if (realPath !== fullPath) {
27596
28398
  try {
27597
- const lstat = fs30.lstatSync(fullPath);
28399
+ const lstat = fs31.lstatSync(fullPath);
27598
28400
  if (lstat.isSymbolicLink()) {
27599
28401
  continue;
27600
28402
  }
@@ -27615,12 +28417,12 @@ function collectFiles(rootDir, includeSet, optionalSet) {
27615
28417
  function collectAllFiles(dir) {
27616
28418
  const files = [];
27617
28419
  try {
27618
- const entries2 = fs30.readdirSync(dir, { withFileTypes: true });
28420
+ const entries2 = fs31.readdirSync(dir, { withFileTypes: true });
27619
28421
  for (const entry of entries2) {
27620
28422
  if (shouldExclude(entry.name)) continue;
27621
- const fullPath = path32.join(dir, entry.name);
28423
+ const fullPath = path33.join(dir, entry.name);
27622
28424
  try {
27623
- if (fs30.lstatSync(fullPath).isSymbolicLink()) continue;
28425
+ if (fs31.lstatSync(fullPath).isSymbolicLink()) continue;
27624
28426
  } catch {
27625
28427
  }
27626
28428
  if (entry.isDirectory()) {
@@ -27635,16 +28437,16 @@ function collectAllFiles(dir) {
27635
28437
  }
27636
28438
  function readMainEngineUuid(agentsDir) {
27637
28439
  try {
27638
- const platformMapPath = path32.join(agentsDir, "main", "sessions", "platform-map.json");
27639
- const indexMapPath = path32.join(agentsDir, "main", "sessions", "session-index.json");
27640
- if (!fs30.existsSync(platformMapPath) || !fs30.existsSync(indexMapPath)) return null;
27641
- const platformMap = JSON.parse(fs30.readFileSync(platformMapPath, "utf-8"));
28440
+ const platformMapPath = path33.join(agentsDir, "main", "sessions", "platform-map.json");
28441
+ const indexMapPath = path33.join(agentsDir, "main", "sessions", "session-index.json");
28442
+ if (!fs31.existsSync(platformMapPath) || !fs31.existsSync(indexMapPath)) return null;
28443
+ const platformMap = JSON.parse(fs31.readFileSync(platformMapPath, "utf-8"));
27642
28444
  const mainPlatformId = platformMap["scope:main"] || platformMap["main"];
27643
28445
  if (!mainPlatformId) return null;
27644
- const indexMap = JSON.parse(fs30.readFileSync(indexMapPath, "utf-8"));
28446
+ const indexMap = JSON.parse(fs31.readFileSync(indexMapPath, "utf-8"));
27645
28447
  const entry = indexMap[mainPlatformId];
27646
28448
  if (!entry || !entry.file) return null;
27647
- const basename10 = path32.basename(entry.file).replace(/\.jsonl$/, "");
28449
+ const basename10 = path33.basename(entry.file).replace(/\.jsonl$/, "");
27648
28450
  return basename10;
27649
28451
  } catch {
27650
28452
  return null;
@@ -27652,13 +28454,13 @@ function readMainEngineUuid(agentsDir) {
27652
28454
  }
27653
28455
  function collectRecentSessions(agentsDir, _days = 7) {
27654
28456
  const files = [];
27655
- if (!fs30.existsSync(agentsDir)) return files;
28457
+ if (!fs31.existsSync(agentsDir)) return files;
27656
28458
  const sessionGroups = /* @__PURE__ */ new Map();
27657
28459
  function scanSessionsDir(dir) {
27658
28460
  try {
27659
- const entries2 = fs30.readdirSync(dir, { withFileTypes: true });
28461
+ const entries2 = fs31.readdirSync(dir, { withFileTypes: true });
27660
28462
  for (const entry of entries2) {
27661
- const fullPath = path32.join(dir, entry.name);
28463
+ const fullPath = path33.join(dir, entry.name);
27662
28464
  if (entry.isDirectory()) {
27663
28465
  scanSessionsDir(fullPath);
27664
28466
  continue;
@@ -27682,7 +28484,7 @@ function collectRecentSessions(agentsDir, _days = 7) {
27682
28484
  if (entry.name.endsWith(".jsonl")) {
27683
28485
  group.jsonl = fullPath;
27684
28486
  } else {
27685
- const stat4 = fs30.statSync(fullPath);
28487
+ const stat4 = fs31.statSync(fullPath);
27686
28488
  group.archived.push({ file: fullPath, mtime: stat4.mtimeMs });
27687
28489
  }
27688
28490
  }
@@ -27701,21 +28503,21 @@ function collectRecentSessions(agentsDir, _days = 7) {
27701
28503
  }
27702
28504
  function collectExportFileSet(stateDir, workspace) {
27703
28505
  const wsFiles = collectFiles(workspace, WORKSPACE_INCLUDE, WORKSPACE_OPTIONAL);
27704
- const sessionFiles = collectRecentSessions(path32.join(stateDir, "agents"), 7);
27705
- const configsDir = path32.join(stateDir, "configs");
28506
+ const sessionFiles = collectRecentSessions(path33.join(stateDir, "agents"), 7);
28507
+ const configsDir = path33.join(stateDir, "configs");
27706
28508
  const configFiles = [];
27707
- if (fs30.existsSync(configsDir)) {
28509
+ if (fs31.existsSync(configsDir)) {
27708
28510
  for (const f of collectAllFiles(configsDir)) {
27709
- if (shouldExclude(path32.basename(f))) continue;
28511
+ if (shouldExclude(path33.basename(f))) continue;
27710
28512
  configFiles.push(f);
27711
28513
  }
27712
28514
  }
27713
28515
  const EVEROS_SKIP = /* @__PURE__ */ new Set([".tmp", ".lock", "import.log", "import_progress.json"]);
27714
- const everosDir = path32.join(stateDir, ".everos");
28516
+ const everosDir = path33.join(stateDir, ".everos");
27715
28517
  const everosFiles = [];
27716
- if (fs30.existsSync(everosDir)) {
28518
+ if (fs31.existsSync(everosDir)) {
27717
28519
  for (const f of collectAllFiles(everosDir)) {
27718
- if (EVEROS_SKIP.has(path32.basename(f))) continue;
28520
+ if (EVEROS_SKIP.has(path33.basename(f))) continue;
27719
28521
  everosFiles.push(f);
27720
28522
  }
27721
28523
  }
@@ -27723,13 +28525,13 @@ function collectExportFileSet(stateDir, workspace) {
27723
28525
  }
27724
28526
  async function doExport(opts) {
27725
28527
  const { agentName, stateDir, note, dryRun: dryRun2 } = opts;
27726
- const workspace = (opts.workspace || path32.join(stateDir, "workspace")).replace(/[/\\]+$/, "");
27727
- const engineHome = path32.join(os5.homedir(), ".engine7").replace(/[/\\]+$/, "");
28528
+ const workspace = (opts.workspace || path33.join(stateDir, "workspace")).replace(/[/\\]+$/, "");
28529
+ const engineHome = path33.join(os5.homedir(), ".engine7").replace(/[/\\]+$/, "");
27728
28530
  console.log(`\u{1F4E6} engine7 export`);
27729
28531
  console.log(` agent: ${agentName}`);
27730
28532
  console.log(` state: ${stateDir}`);
27731
28533
  console.log(` workspace: ${workspace}`);
27732
- if (!fs30.existsSync(workspace)) {
28534
+ if (!fs31.existsSync(workspace)) {
27733
28535
  console.error(`\u274C workspace \u4E0D\u5B58\u5728: ${workspace}`);
27734
28536
  process.exit(1);
27735
28537
  }
@@ -27738,16 +28540,16 @@ async function doExport(opts) {
27738
28540
  console.log(` workspace \u6587\u4EF6: ${wsFiles.length}`);
27739
28541
  console.log(` session jsonl: ${sessionFiles.length} (\u6700\u8FD17\u5929)`);
27740
28542
  console.log(` configs: ${configFiles.length}`);
27741
- console.log(` everos: ${everosFiles.length} (${(everosFiles.reduce((s, f) => s + fs30.statSync(f).size, 0) / 1024 / 1024).toFixed(1)} MB)`);
28543
+ console.log(` everos: ${everosFiles.length} (${(everosFiles.reduce((s, f) => s + fs31.statSync(f).size, 0) / 1024 / 1024).toFixed(1)} MB)`);
27742
28544
  if (dryRun2) {
27743
28545
  console.log(`
27744
28546
  [DRY RUN] \u4F1A\u6253\u5305\u4EE5\u4E0B\u6587\u4EF6:`);
27745
- wsFiles.slice(0, 20).forEach((f) => console.log(` ${path32.relative(workspace, f)}`));
28547
+ wsFiles.slice(0, 20).forEach((f) => console.log(` ${path33.relative(workspace, f)}`));
27746
28548
  if (wsFiles.length > 20) console.log(` ... \u8FD8\u6709 ${wsFiles.length - 20} \u4E2A\u6587\u4EF6`);
27747
28549
  if (everosFiles.length > 0) {
27748
28550
  console.log(`
27749
28551
  [DRY RUN] everos (${everosFiles.length} \u6587\u4EF6):`);
27750
- everosFiles.slice(0, 10).forEach((f) => console.log(` ${path32.relative(stateDir, f)}`));
28552
+ everosFiles.slice(0, 10).forEach((f) => console.log(` ${path33.relative(stateDir, f)}`));
27751
28553
  if (everosFiles.length > 10) console.log(` ... \u8FD8\u6709 ${everosFiles.length - 10} \u4E2A\u6587\u4EF6`);
27752
28554
  }
27753
28555
  console.log(`
@@ -27755,46 +28557,46 @@ async function doExport(opts) {
27755
28557
  return;
27756
28558
  }
27757
28559
  const version = formatLog().replace(/[-: ]/g, "").slice(0, 12).replace(/(d{8})(d{4})/, "$1-$2");
27758
- const tmpDir = path32.join(os5.tmpdir(), `engine7-export-${agentName}-${version}`);
27759
- fs30.mkdirSync(tmpDir, { recursive: true });
27760
- const stagingDir = path32.join(tmpDir, agentName);
27761
- fs30.mkdirSync(stagingDir, { recursive: true });
28560
+ const tmpDir = path33.join(os5.tmpdir(), `engine7-export-${agentName}-${version}`);
28561
+ fs31.mkdirSync(tmpDir, { recursive: true });
28562
+ const stagingDir = path33.join(tmpDir, agentName);
28563
+ fs31.mkdirSync(stagingDir, { recursive: true });
27762
28564
  let fileCount = 0;
27763
28565
  let totalSize = 0;
27764
28566
  let copyFail = 0;
27765
28567
  const copyOne = (srcFile, relRoot, destRoot) => {
27766
- const relPath = path32.relative(relRoot, srcFile);
27767
- const destFile = path32.join(destRoot, relPath);
27768
- fs30.mkdirSync(path32.dirname(destFile), { recursive: true });
28568
+ const relPath = path33.relative(relRoot, srcFile);
28569
+ const destFile = path33.join(destRoot, relPath);
28570
+ fs31.mkdirSync(path33.dirname(destFile), { recursive: true });
27769
28571
  try {
27770
28572
  if (isTextFile(srcFile)) {
27771
- const content = fs30.readFileSync(srcFile, "utf-8");
27772
- fs30.writeFileSync(destFile, sanitizeContent(content, dirs));
28573
+ const content = fs31.readFileSync(srcFile, "utf-8");
28574
+ fs31.writeFileSync(destFile, sanitizeContent(content, dirs));
27773
28575
  } else {
27774
- fs30.copyFileSync(srcFile, destFile);
28576
+ fs31.copyFileSync(srcFile, destFile);
27775
28577
  }
27776
28578
  fileCount++;
27777
- totalSize += fs30.statSync(srcFile).size;
28579
+ totalSize += fs31.statSync(srcFile).size;
27778
28580
  } catch (e) {
27779
28581
  copyFail++;
27780
28582
  console.warn(` \u26A0\uFE0F \u62F7\u8D1D\u5931\u8D25: ${relPath} (${e.code || e.message})`);
27781
28583
  }
27782
28584
  };
27783
- for (const srcFile of wsFiles) copyOne(srcFile, workspace, path32.join(stagingDir, "workspace"));
28585
+ for (const srcFile of wsFiles) copyOne(srcFile, workspace, path33.join(stagingDir, "workspace"));
27784
28586
  for (const srcFile of sessionFiles) {
27785
- const relPath = path32.relative(stateDir, srcFile);
27786
- const destFile = path32.join(stagingDir, relPath);
27787
- fs30.mkdirSync(path32.dirname(destFile), { recursive: true });
28587
+ const relPath = path33.relative(stateDir, srcFile);
28588
+ const destFile = path33.join(stagingDir, relPath);
28589
+ fs31.mkdirSync(path33.dirname(destFile), { recursive: true });
27788
28590
  try {
27789
- let content = fs30.readFileSync(srcFile, "utf-8");
28591
+ let content = fs31.readFileSync(srcFile, "utf-8");
27790
28592
  content = sanitizeContent(content, dirs);
27791
- const baseName = path32.basename(srcFile);
28593
+ const baseName = path33.basename(srcFile);
27792
28594
  if (baseName === "session-index.json" || baseName === "platform-map.json") {
27793
28595
  content = content.split("\\\\").join("/");
27794
28596
  }
27795
- fs30.writeFileSync(destFile, content);
28597
+ fs31.writeFileSync(destFile, content);
27796
28598
  fileCount++;
27797
- totalSize += fs30.statSync(srcFile).size;
28599
+ totalSize += fs31.statSync(srcFile).size;
27798
28600
  } catch (e) {
27799
28601
  copyFail++;
27800
28602
  console.warn(` \u26A0\uFE0F \u62F7\u8D1D\u5931\u8D25: ${relPath} (${e.code || e.message})`);
@@ -27816,13 +28618,13 @@ async function doExport(opts) {
27816
28618
  totalSize,
27817
28619
  note
27818
28620
  };
27819
- fs30.writeFileSync(path32.join(stagingDir, "manifest.json"), JSON.stringify(manifest, null, 2));
27820
- const archiveFile = path32.join(tmpDir, `${agentName}-${version}.tar.gz`);
28621
+ fs31.writeFileSync(path33.join(stagingDir, "manifest.json"), JSON.stringify(manifest, null, 2));
28622
+ const archiveFile = path33.join(tmpDir, `${agentName}-${version}.tar.gz`);
27821
28623
  console.log(`
27822
28624
  \u{1F5DC}\uFE0F \u6253\u5305\u4E2D...`);
27823
28625
  const tarExe = process.platform === "win32" ? "C:\\Windows\\System32\\tar.exe" : "tar";
27824
28626
  execSync2(`"${tarExe}" -czf "${archiveFile}" -C "${tmpDir}" ${agentName}`, { stdio: "pipe" });
27825
- const archiveSize = fs30.statSync(archiveFile).size;
28627
+ const archiveSize = fs31.statSync(archiveFile).size;
27826
28628
  console.log(`\u2705 \u6253\u5305\u5B8C\u6210: ${archiveFile} (${(archiveSize / 1024 / 1024).toFixed(1)} MB, ${fileCount} \u6587\u4EF6${copyFail ? `, \u5931\u8D25 ${copyFail}` : ""})`);
27827
28629
  const cfg = loadTravelConfig();
27828
28630
  if (!cfg || !cfg.githubToken) {
@@ -27848,12 +28650,12 @@ function parseImportArgs(args) {
27848
28650
  else if (args[i] === "--dry-run") dryRun2 = true;
27849
28651
  }
27850
28652
  if (!stateDir) throw new Error("import \u9700\u8981 --state-dir <path>");
27851
- if (!agentName) agentName = path32.basename(stateDir);
28653
+ if (!agentName) agentName = path33.basename(stateDir);
27852
28654
  return { stateDir, agentName, version, archiveFile, dryRun: dryRun2 };
27853
28655
  }
27854
28656
  async function resolveArchiveForTest(opts) {
27855
28657
  if (opts.archiveFile) {
27856
- if (!fs30.existsSync(opts.archiveFile)) {
28658
+ if (!fs31.existsSync(opts.archiveFile)) {
27857
28659
  throw new Error(`\u672C\u5730\u5305\u4E0D\u5B58\u5728: ${opts.archiveFile}`);
27858
28660
  }
27859
28661
  return opts.archiveFile;
@@ -27865,8 +28667,8 @@ async function doImport(opts) {
27865
28667
  console.log(`\u{1F4E5} engine7 import`);
27866
28668
  console.log(` agent: ${agentName}`);
27867
28669
  console.log(` target: ${stateDir}`);
27868
- const tmpDir = path32.join(os5.tmpdir(), `engine7-import-${agentName}-${Date.now()}`);
27869
- fs30.mkdirSync(tmpDir, { recursive: true });
28670
+ const tmpDir = path33.join(os5.tmpdir(), `engine7-import-${agentName}-${Date.now()}`);
28671
+ fs31.mkdirSync(tmpDir, { recursive: true });
27870
28672
  const resolved = await resolveArchiveForTest({
27871
28673
  archiveFile: archiveFile || "",
27872
28674
  tmpDir,
@@ -27891,99 +28693,99 @@ async function doImport(opts) {
27891
28693
  console.log(`\u{1F4C2} \u89E3\u5305\u4E2D...`);
27892
28694
  const tarExe = process.platform === "win32" ? "C:\\Windows\\System32\\tar.exe" : "tar";
27893
28695
  execSync2(`"${tarExe}" -xzf "${finalArchive}" -C "${tmpDir}"`, { stdio: "pipe" });
27894
- const stagingDir = path32.join(tmpDir, agentName);
27895
- const manifestPath = path32.join(stagingDir, "manifest.json");
27896
- if (!fs30.existsSync(manifestPath)) {
28696
+ const stagingDir = path33.join(tmpDir, agentName);
28697
+ const manifestPath = path33.join(stagingDir, "manifest.json");
28698
+ if (!fs31.existsSync(manifestPath)) {
27897
28699
  console.error(`\u274C manifest.json \u4E0D\u5B58\u5728\uFF0C\u6587\u4EF6\u53EF\u80FD\u635F\u574F`);
27898
28700
  process.exit(1);
27899
28701
  }
27900
- const manifest = JSON.parse(fs30.readFileSync(manifestPath, "utf-8"));
27901
- const workspace = path32.join(stateDir, "workspace").replace(/\\/g, "/").replace(/[/\\]+$/, "");
27902
- const engineHome = path32.join(os5.homedir(), ".engine7").replace(/\\/g, "/").replace(/[/\\]+$/, "");
28702
+ const manifest = JSON.parse(fs31.readFileSync(manifestPath, "utf-8"));
28703
+ const workspace = path33.join(stateDir, "workspace").replace(/\\/g, "/").replace(/[/\\]+$/, "");
28704
+ const engineHome = path33.join(os5.homedir(), ".engine7").replace(/\\/g, "/").replace(/[/\\]+$/, "");
27903
28705
  const dirs = { workspace, stateDir: stateDir.replace(/\\/g, "/").replace(/[/\\]+$/, ""), engineHome };
27904
28706
  console.log(` \u7248\u672C: ${manifest.version}`);
27905
28707
  console.log(` \u521B\u5EFA: ${manifest.createdAt}`);
27906
28708
  console.log(` \u6587\u4EF6\u6570: ${manifest.fileCount}`);
27907
- const wsStaging = path32.join(stagingDir, "workspace");
28709
+ const wsStaging = path33.join(stagingDir, "workspace");
27908
28710
  let restoredCount = 0;
27909
28711
  let skipCount = 0;
27910
- if (fs30.existsSync(wsStaging)) {
28712
+ if (fs31.existsSync(wsStaging)) {
27911
28713
  const allFiles = collectAllFiles(wsStaging);
27912
28714
  for (const srcFile of allFiles) {
27913
- const relPath = path32.relative(wsStaging, srcFile);
27914
- const destFile = path32.join(workspace, relPath);
27915
- fs30.mkdirSync(path32.dirname(destFile), { recursive: true });
28715
+ const relPath = path33.relative(wsStaging, srcFile);
28716
+ const destFile = path33.join(workspace, relPath);
28717
+ fs31.mkdirSync(path33.dirname(destFile), { recursive: true });
27916
28718
  try {
27917
28719
  if (isTextFile(srcFile)) {
27918
- const content = fs30.readFileSync(srcFile, "utf-8");
27919
- fs30.writeFileSync(destFile, restoreContent(content, dirs));
28720
+ const content = fs31.readFileSync(srcFile, "utf-8");
28721
+ fs31.writeFileSync(destFile, restoreContent(content, dirs));
27920
28722
  } else {
27921
- fs30.copyFileSync(srcFile, destFile);
28723
+ fs31.copyFileSync(srcFile, destFile);
27922
28724
  }
27923
28725
  restoredCount++;
27924
28726
  } catch (e) {
27925
28727
  skipCount++;
27926
- console.log(` \u26A0\uFE0F \u8DF3\u8FC7: ${path32.relative(wsStaging, srcFile)} (${e.code || e.message})`);
28728
+ console.log(` \u26A0\uFE0F \u8DF3\u8FC7: ${path33.relative(wsStaging, srcFile)} (${e.code || e.message})`);
27927
28729
  }
27928
28730
  }
27929
28731
  }
27930
- const agentsStaging = path32.join(stagingDir, "agents");
27931
- if (fs30.existsSync(agentsStaging)) {
28732
+ const agentsStaging = path33.join(stagingDir, "agents");
28733
+ if (fs31.existsSync(agentsStaging)) {
27932
28734
  const sessionFiles = collectAllFiles(agentsStaging);
27933
28735
  for (const srcFile of sessionFiles) {
27934
- const relPath = path32.relative(stagingDir, srcFile);
27935
- const destFile = path32.join(stateDir, relPath);
27936
- fs30.mkdirSync(path32.dirname(destFile), { recursive: true });
27937
- const content = fs30.readFileSync(srcFile, "utf-8");
27938
- fs30.writeFileSync(destFile, restoreContent(content, dirs));
28736
+ const relPath = path33.relative(stagingDir, srcFile);
28737
+ const destFile = path33.join(stateDir, relPath);
28738
+ fs31.mkdirSync(path33.dirname(destFile), { recursive: true });
28739
+ const content = fs31.readFileSync(srcFile, "utf-8");
28740
+ fs31.writeFileSync(destFile, restoreContent(content, dirs));
27939
28741
  restoredCount++;
27940
28742
  }
27941
28743
  }
27942
- const configsStaging = path32.join(stagingDir, "configs");
27943
- if (fs30.existsSync(configsStaging)) {
28744
+ const configsStaging = path33.join(stagingDir, "configs");
28745
+ if (fs31.existsSync(configsStaging)) {
27944
28746
  const cfgFiles = collectAllFiles(configsStaging);
27945
28747
  for (const srcFile of cfgFiles) {
27946
- const relPath = path32.relative(stagingDir, srcFile);
27947
- const destFile = path32.join(stateDir, relPath);
27948
- fs30.mkdirSync(path32.dirname(destFile), { recursive: true });
28748
+ const relPath = path33.relative(stagingDir, srcFile);
28749
+ const destFile = path33.join(stateDir, relPath);
28750
+ fs31.mkdirSync(path33.dirname(destFile), { recursive: true });
27949
28751
  if (isTextFile(srcFile)) {
27950
- const content = fs30.readFileSync(srcFile, "utf-8");
27951
- fs30.writeFileSync(destFile, restoreContent(content, dirs));
28752
+ const content = fs31.readFileSync(srcFile, "utf-8");
28753
+ fs31.writeFileSync(destFile, restoreContent(content, dirs));
27952
28754
  } else {
27953
- fs30.copyFileSync(srcFile, destFile);
28755
+ fs31.copyFileSync(srcFile, destFile);
27954
28756
  }
27955
28757
  restoredCount++;
27956
28758
  }
27957
28759
  }
27958
- const everosStaging = path32.join(stagingDir, ".everos");
27959
- if (fs30.existsSync(everosStaging)) {
28760
+ const everosStaging = path33.join(stagingDir, ".everos");
28761
+ if (fs31.existsSync(everosStaging)) {
27960
28762
  const everosFiles = collectAllFiles(everosStaging);
27961
28763
  for (const srcFile of everosFiles) {
27962
- const relPath = path32.relative(stagingDir, srcFile);
27963
- const destFile = path32.join(stateDir, relPath);
27964
- fs30.mkdirSync(path32.dirname(destFile), { recursive: true });
28764
+ const relPath = path33.relative(stagingDir, srcFile);
28765
+ const destFile = path33.join(stateDir, relPath);
28766
+ fs31.mkdirSync(path33.dirname(destFile), { recursive: true });
27965
28767
  try {
27966
28768
  if (isTextFile(srcFile)) {
27967
- const content = fs30.readFileSync(srcFile, "utf-8");
27968
- fs30.writeFileSync(destFile, restoreContent(content, dirs));
28769
+ const content = fs31.readFileSync(srcFile, "utf-8");
28770
+ fs31.writeFileSync(destFile, restoreContent(content, dirs));
27969
28771
  } else {
27970
- fs30.copyFileSync(srcFile, destFile);
28772
+ fs31.copyFileSync(srcFile, destFile);
27971
28773
  }
27972
28774
  restoredCount++;
27973
28775
  } catch (e) {
27974
28776
  skipCount++;
27975
- console.log(` \u26A0\uFE0F \u8DF3\u8FC7: ${path32.relative(everosStaging, srcFile)} (${e.code || e.message})`);
28777
+ console.log(` \u26A0\uFE0F \u8DF3\u8FC7: ${path33.relative(everosStaging, srcFile)} (${e.code || e.message})`);
27976
28778
  }
27977
28779
  }
27978
28780
  console.log(` everos: ${everosFiles.length} \u6587\u4EF6\u5DF2\u6062\u590D`);
27979
28781
  }
27980
28782
  console.log(`\u2705 \u6062\u590D\u5B8C\u6210: ${restoredCount} \u6587\u4EF6 \u2192 ${stateDir}${skipCount ? ` (\u8DF3\u8FC7 ${skipCount})` : ""}`);
27981
28783
  const platformConfigName = process.platform === "darwin" ? "xiaoke-mac.json" : "xiaoke-win.json";
27982
- const restoredConfigPath = path32.join(stateDir, "configs", platformConfigName);
27983
- const configToUse = fs30.existsSync(restoredConfigPath) ? restoredConfigPath : path32.join(stateDir, "configs", "xiaoke.json");
28784
+ const restoredConfigPath = path33.join(stateDir, "configs", platformConfigName);
28785
+ const configToUse = fs31.existsSync(restoredConfigPath) ? restoredConfigPath : path33.join(stateDir, "configs", "xiaoke.json");
27984
28786
  console.log(`
27985
28787
  \u{1F4A1} \u4E0B\u4E00\u6B65: engine7 start --config "${configToUse}"`);
27986
- fs30.rmSync(tmpDir, { recursive: true, force: true });
28788
+ fs31.rmSync(tmpDir, { recursive: true, force: true });
27987
28789
  }
27988
28790
  async function uploadToGitHub(cfg, archiveFile, agentName, version, manifest) {
27989
28791
  const repo = `${cfg.githubOwner}/${cfg.repoName}`;
@@ -28006,618 +28808,205 @@ async function uploadToGitHub(cfg, archiveFile, agentName, version, manifest) {
28006
28808
  prerelease: false,
28007
28809
  make_latest: "true"
28008
28810
  })
28009
- });
28010
- if (!createRes.ok) {
28011
- const err = await createRes.text();
28012
- throw new Error(`\u521B\u5EFA release \u5931\u8D25: ${createRes.status} ${err}`);
28013
- }
28014
- const release2 = await createRes.json();
28015
- const uploadUrl = release2.upload_url.replace("{?name,label}", "");
28016
- const fileBuffer = fs30.readFileSync(archiveFile);
28017
- const fileName = path32.basename(archiveFile);
28018
- const uploadRes = await fetch(`${uploadUrl}?name=${fileName}`, {
28019
- method: "POST",
28020
- headers: {
28021
- "Authorization": `Bearer ${cfg.githubToken}`,
28022
- "Accept": "application/vnd.github+json",
28023
- "Content-Type": "application/gzip",
28024
- "Content-Length": String(fileBuffer.length)
28025
- },
28026
- body: fileBuffer
28027
- });
28028
- if (!uploadRes.ok) {
28029
- const err = await uploadRes.text();
28030
- throw new Error(`\u4E0A\u4F20 asset \u5931\u8D25: ${uploadRes.status} ${err}`);
28031
- }
28032
- console.log(`\u2705 \u4E0A\u4F20\u6210\u529F!`);
28033
- console.log(` release: ${release2.html_url}`);
28034
- } catch (e) {
28035
- console.error(`\u274C GitHub \u4E0A\u4F20\u5931\u8D25: ${e.message}`);
28036
- console.error(` archive \u4ECD\u5728: ${archiveFile}`);
28037
- throw e;
28038
- }
28039
- try {
28040
- fs30.rmSync(path32.dirname(archiveFile), { recursive: true, force: true });
28041
- } catch {
28042
- }
28043
- }
28044
- async function downloadFromGitHub(cfg, agentName, version, destDir) {
28045
- const repo = `${cfg.githubOwner}/${cfg.repoName}`;
28046
- const tag = version || await getLatestReleaseTag(cfg, agentName);
28047
- console.log(`\u2B07\uFE0F \u4E0B\u8F7D: ${repo} release ${tag}`);
28048
- const res = await fetch(`https://api.github.com/repos/${repo}/releases/tags/${tag}`, {
28049
- headers: {
28050
- "Authorization": `Bearer ${cfg.githubToken}`,
28051
- "Accept": "application/vnd.github+json"
28052
- }
28053
- });
28054
- if (!res.ok) {
28055
- throw new Error(`\u83B7\u53D6 release \u5931\u8D25: ${res.status}`);
28056
- }
28057
- const release2 = await res.json();
28058
- const asset = release2.assets?.find((a) => a.name.endsWith(".tar.gz") || a.name.endsWith(".zip"));
28059
- if (!asset) {
28060
- throw new Error(`release ${tag} \u6CA1\u6709 tar.gz/zip asset`);
28061
- }
28062
- const downloadRes = await fetch(asset.url, {
28063
- headers: {
28064
- "Authorization": `Bearer ${cfg.githubToken}`,
28065
- "Accept": "application/octet-stream"
28066
- }
28067
- });
28068
- if (!downloadRes.ok) {
28069
- throw new Error(`\u4E0B\u8F7D asset \u5931\u8D25: ${downloadRes.status}`);
28070
- }
28071
- const buffer = Buffer.from(await downloadRes.arrayBuffer());
28072
- const archiveFile = path32.join(destDir, asset.name);
28073
- fs30.writeFileSync(archiveFile, buffer);
28074
- return archiveFile;
28075
- }
28076
- async function getLatestReleaseTag(cfg, agentName) {
28077
- const repo = `${cfg.githubOwner}/${cfg.repoName}`;
28078
- const res = await fetch(`https://api.github.com/repos/${repo}/releases?per_page=30`, {
28079
- headers: {
28080
- "Authorization": `Bearer ${cfg.githubToken}`,
28081
- "Accept": "application/vnd.github+json"
28082
- }
28083
- });
28084
- if (!res.ok) {
28085
- throw new Error(`\u83B7\u53D6 release \u5217\u8868\u5931\u8D25: ${res.status}`);
28086
- }
28087
- const releases = await res.json();
28088
- const match = releases.find((r) => r.tag_name?.startsWith(`${agentName}-`));
28089
- if (!match) {
28090
- throw new Error(`\u6CA1\u6709\u627E\u5230 ${agentName} \u7684 release`);
28091
- }
28092
- return match.tag_name;
28093
- }
28094
- var CONFIG_PATH, PATH_PLACEHOLDERS, WORKSPACE_INCLUDE, WORKSPACE_OPTIONAL, WORKSPACE_EXCLUDE, TEXT_EXTENSIONS;
28095
- var init_cli_travel = __esm({
28096
- "src/cli-travel.ts"() {
28097
- "use strict";
28098
- init_clock();
28099
- CONFIG_PATH = path32.join(os5.homedir(), ".engine7-travel.json");
28100
- PATH_PLACEHOLDERS = [
28101
- { placeholder: "{{WORKSPACE}}", getOriginal: (d) => d.workspace },
28102
- { placeholder: "{{ENGINE_HOME}}", getOriginal: (d) => d.engineHome },
28103
- { placeholder: "{{STATE_DIR}}", getOriginal: (d) => d.stateDir }
28104
- ];
28105
- WORKSPACE_INCLUDE = /* @__PURE__ */ new Set([
28106
- // 核心文件
28107
- "AGENTS.md",
28108
- "SOUL.md",
28109
- "MEMORY.md",
28110
- "USER.md",
28111
- "HEARTBEAT.md",
28112
- "INDEX.md",
28113
- "SESSION-STATE.md",
28114
- // 核心目录
28115
- "prompts",
28116
- "topics",
28117
- "memory",
28118
- "inner-voice",
28119
- "docs",
28120
- "skills",
28121
- "voice-chat",
28122
- "scripts",
28123
- "selfie",
28124
- "moodboard",
28125
- // 状态文件 + 目录
28126
- ".calendar",
28127
- "nudge-state.json"
28128
- ]);
28129
- WORKSPACE_OPTIONAL = /* @__PURE__ */ new Set([
28130
- "images",
28131
- "tools"
28132
- ]);
28133
- WORKSPACE_EXCLUDE = /* @__PURE__ */ new Set([
28134
- "livestream",
28135
- "content-library",
28136
- "tmp",
28137
- ".git",
28138
- "node_modules",
28139
- "memory_runs",
28140
- "workspace",
28141
- "prompt-archive",
28142
- "aim-archive",
28143
- "test-agent",
28144
- "nul",
28145
- "*.bak*",
28146
- "*.bak-*",
28147
- "~$*"
28148
- ]);
28149
- TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
28150
- ".md",
28151
- ".json",
28152
- ".txt",
28153
- ".js",
28154
- ".ts",
28155
- ".mjs",
28156
- ".py",
28157
- ".yaml",
28158
- ".yml",
28159
- ".cmd",
28160
- ".bat",
28161
- ".sh",
28162
- ".csv",
28163
- ".html",
28164
- ".toml",
28165
- ".list"
28166
- // .everos 的 everos.toml/config.toml/ome.toml/everos-env.list
28167
- ]);
28168
- }
28169
- });
28170
-
28171
- // src/cli-init.ts
28172
- import * as path33 from "node:path";
28173
- import * as fs31 from "node:fs";
28174
-
28175
- // src/cli-service.ts
28176
- function parseServiceArgs(args) {
28177
- const action = args[0] || "";
28178
- let configPath = "";
28179
- let cwd = process.cwd();
28180
- let nodeExe = "";
28181
- for (let i = 1; i < args.length; i++) {
28182
- if (args[i] === "--config" && args[i + 1]) configPath = args[++i];
28183
- else if (args[i] === "--cwd" && args[i + 1]) cwd = args[++i];
28184
- else if (args[i] === "--node-exe" && args[i + 1]) nodeExe = args[++i];
28185
- }
28186
- return { action, configPath, cwd, nodeExe };
28187
- }
28188
- function buildServiceWrapper(opts) {
28189
- const nodeExe = opts.nodeExe || process.execPath;
28190
- return `@echo off\r
28191
- cd /d "${opts.cwd}"\r
28192
- "${nodeExe}" "${opts.cliMjs}" start --config "${opts.configPath}"\r
28193
- `;
28194
- }
28195
-
28196
- // src/cli-init.ts
28197
- init_feishu_quick_register();
28198
- init_wechat();
28199
- init_qr_render();
28200
- init_cli_events();
28201
- import * as readline3 from "node:readline";
28202
- import { fileURLToPath as fileURLToPath7 } from "node:url";
28203
-
28204
- // src/cli-init-answers.ts
28205
- init_clock();
28206
- import * as fs2 from "node:fs";
28207
-
28208
- // src/cli-init-config.ts
28209
- import * as path2 from "node:path";
28210
- var SCHEMA_VERSION = 1;
28211
- var FREE_TIER_RELAY_URL = "https://www.twinsun.top:8866";
28212
- function isFreeTier(v) {
28213
- return v.freeTier === true || v.primaryProvider === "7yi";
28214
- }
28215
- function resolveRelayUrl(relayArg, env = process.env) {
28216
- return (relayArg || "").trim() || (env.E7_RELAY_URL || "").trim() || FREE_TIER_RELAY_URL;
28217
- }
28218
- var DEFAULT_PRIMARY_MODEL = {
28219
- dashscope: "dashscope/qwen3.8-max",
28220
- minimax: "minimax/MiniMax-M3",
28221
- zhipu: "zhipu/glm-5.3",
28222
- deepseek: "deepseek/deepseek-v4-pro",
28223
- "7yi": "7yi/engine7-free"
28224
- };
28225
- var VISION_CAPABLE = {
28226
- // 0917:原先标 true,但同一个文件里 qwen3.8-max 的 input 是 ['text'],安装器目录
28227
- // 也明确标 vision=false(只有 Flash 叫"多模态")。矛盾直接导致**默认 CLI 装机路径**
28228
- // 重演 P0-2 的症状:vision 复用主模型 → 该型号 modelInputs=['text'] → 引擎把历史和
28229
- // 当前消息里的图全剥成 "[image]"。改成 false 后 resolveVisionModel 会落到
28230
- // qwen3.8-flash(input 真含 image)。
28231
- "dashscope/qwen3.8-max": false,
28232
- "dashscope/qwen3.8-flash": true,
28233
- "minimax/MiniMax-M3": true,
28234
- // 0918 移除 'minimax/MiniMax-M2.7'(内置表同时去掉该型号)
28235
- "zhipu/glm-5v-turbo": true,
28236
- "zhipu/glm-5.3": false,
28237
- // 0917:DeepSeek 全线按支持图片处理。依据(官方更新日志 + Vision 文档):
28238
- // - `deepseek-flash`(V4.1 Flash)原生多模态,官方 Vision 文档明确收图
28239
- // https://api-docs.deepseek.com/guides/vision
28240
- // - `deepseek-v4-flash` / `deepseek-v4-flash-vision-exp`:退役名,请求由 V4.1 Flash
28241
- // 承接(临时兼容路由,官方未给移除日期)→ 同样收图
28242
- // - `deepseek-v4-pro`:官方旗舰已停,9/14 起请求同样由 V4.1 Flash 承接;新的
28243
- // deepseek-pro 发布后大概率也走多模态(flash 都支持了,pro 不会倒退)
28244
- // 原先两个都标 false:v4-pro 标 false 会让今天用它的 agent **静默丢图**,
28245
- // deepseek-flash 标 false 还会触发 P0-2 的幽灵引用兜底。
28246
- // 声明多一分的代价是上游响亮报错,少一分的代价是无声丢图——选前者。
28247
- "deepseek/deepseek-flash": true,
28248
- "deepseek/deepseek-v4-flash": true,
28249
- "deepseek/deepseek-v4-pro": true,
28250
- // 免费档(0919):engine7-free 只收文本,engine7-vis-free 才带 image。
28251
- // 标错会让主模型被当成能看图 → 图发给一个 input=['text'] 的型号 → 静默丢图。
28252
- "7yi/engine7-free": false,
28253
- "7yi/engine7-vis-free": true
28254
- };
28255
- var PROVIDER_DEFS = {
28256
- dashscope: {
28257
- // 双通道(0916):按量付费(sk-)走 compatible-mode;Token Plan 订阅(sk-sp-)走专属域名,
28258
- // 两边密钥/端点不可互换——用错就"订阅了还按量计费"。计划选择见 dashscopePlan。
28259
- baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",
28260
- api: "openai-completions",
28261
- models: [
28262
- { id: "qwen3.8-max", name: "Qwen 3.8 Max", reasoning: true, input: ["text"], contextWindow: 1e6, maxTokens: 65536 },
28263
- { id: "qwen3.8-flash", name: "Qwen 3.8 Flash\uFF08\u591A\u6A21\u6001\uFF09", reasoning: true, input: ["text", "image"], contextWindow: 1e6, maxTokens: 65536 }
28264
- ]
28265
- },
28266
- minimax: {
28267
- baseUrl: "https://api.minimaxi.com/anthropic",
28268
- api: "anthropic",
28269
- models: [
28270
- // 0918 去掉 MiniMax-M2.7:有 M3 之后没人用了(翀哥)
28271
- { id: "MiniMax-M3", name: "MiniMax M3", reasoning: true, input: ["text", "image"], contextWindow: 1e6, maxTokens: 64e3 }
28272
- ]
28273
- },
28274
- zhipu: {
28275
- baseUrl: "https://open.bigmodel.cn/api/coding/paas/v4",
28276
- api: "openai-completions",
28277
- models: [
28278
- { id: "glm-5.3", name: "GLM-5.3 (\u6700\u65B0\u4E3B\u529B)", reasoning: true, input: ["text"], contextWindow: 1e6, maxTokens: 128e3 },
28279
- { id: "glm-5v-turbo", name: "GLM-5V-Turbo (\u89C6\u89C9)", reasoning: true, input: ["text", "image"], contextWindow: 2e5, maxTokens: 128e3 }
28280
- ]
28281
- },
28282
- deepseek: {
28283
- baseUrl: "https://api.deepseek.com/anthropic",
28284
- api: "anthropic",
28285
- thinking: { enabled: true, budgetTokens: 8192 },
28286
- models: [
28287
- // 0916 改名:deepseek-v4-flash → deepseek-flash(旧名仍可调,请求由 V4.1 Flash 承接)
28288
- // 0917 补 image:V4.1 Flash 原生多模态(见 VISION_CAPABLE 注释)
28289
- { id: "deepseek-flash", name: "DeepSeek Flash", reasoning: true, input: ["text", "image"], contextWindow: 1e6, maxTokens: 384e3 },
28290
- // 0917 补 image:官方旗舰已停、9/14 起请求由 V4.1 Flash 承接,实际收图
28291
- { id: "deepseek-v4-pro", name: "DeepSeek V4 Pro", reasoning: true, input: ["text", "image"], contextWindow: 1e6, maxTokens: 384e3 }
28292
- ]
28293
- },
28294
- "7yi": {
28295
- // 引擎免费档的中转站。baseUrl 是兜底值——真正的权威值来自领号响应
28296
- // (服务端按请求 Host 自报地址),由安装器作为 customBaseUrl 下发。
28297
- // 这样挪机器、换域名只改服务端,不用重发安装包。
28298
- //
28299
- // ⚠️ 0921 修:这里原本写的是 `https://www.twinsun.top/v1`(443),而 **443 上不是
28300
- // 中转站**(实测回的是另一个服务的 FastAPI 404)。安装器那条路每次都会下发
28301
- // customBaseUrl 把它盖掉,所以正式装机撞不到;但**终端向导选 7yi 时不问 baseUrl**,
28302
- // 直接落到这个兜底值——于是命令行用户拿到的是「号领到了、引擎起来了、一发消息就废」。
28303
- // 用常量拼,别再手写一遍(漏端口就是这个坑)。
28304
- baseUrl: `${FREE_TIER_RELAY_URL}/v1`,
28305
- api: "anthropic",
28306
- models: [
28307
- // ⚠️ contextWindow **不能照抄 1000000**。
28308
- //
28309
- // engine7-free 是虚拟模型,真实上限取决于当时哪一档在接。写满了会让压缩阈值
28310
- // 高到永不触发——实测病例(0919 小柯):声明 1M → 阈值 906137,而她的上下文
28311
- // 已滚到 191080 仍不压缩,最后那条 191k 的请求把 SenseNova 的 TPM 打爆
28312
- // (429 inference exceeds tpm/rpm limit),整条链退到付费的 MiniMax。
28313
- //
28314
- // 实测数据点(0919,SenseNova 那一档):
28315
- // 114K token → 成功(10s,真实输出)
28316
- // 191K token → 失败(tpm/rpm 429)
28317
- // 分界在 114K~191K 之间,中点无数据。
28318
- //
28319
- // 阈值与 contextWindow 是比例关系:实测 1,000,000 → 906,137(×0.906)。
28320
- // 取 150000 → 阈值约 136K,在成功线上方留 22K 余量,又不去赌无数据区。
28321
- // 等中点实测出来再放宽。
28322
- { id: "engine7-free", name: "Engine7 \u514D\u8D39\uFF08\u6587\u672C\uFF09", reasoning: true, input: ["text"], contextWindow: 15e4, maxTokens: 64e3 },
28323
- // input 带 image:实测 Claude 格式的图片块经 /v1/messages 能被上游正确识别
28324
- { id: "engine7-vis-free", name: "Engine7 \u514D\u8D39\uFF08\u89C6\u89C9\uFF09", reasoning: true, input: ["text", "image"], contextWindow: 15e4, maxTokens: 64e3 }
28325
- // 注意:engine7-img-free 不在这里。它在 chat 端点是 404(不是 chat 模型),
28326
- // 生图由 my_selfie 自己拼 /v1/images/edits,relayModel 默认值已经是它。
28327
- ]
28328
- }
28329
- };
28330
- var MODEL_CHOICES = Object.fromEntries(
28331
- Object.entries(PROVIDER_DEFS).map(([pid, def]) => [
28332
- pid,
28333
- def.models.map((m) => `${pid}/${m.id}`)
28334
- ])
28335
- );
28336
- var CUSTOM_DEFAULT_INPUT = ["text", "image"];
28337
- function modelIdOf(ref) {
28338
- const i = ref.indexOf("/");
28339
- return i >= 0 ? ref.slice(i + 1) : ref;
28340
- }
28341
- function menuProviderOf(providerId) {
28342
- return PROVIDER_DEFS[providerId] ? providerId : "custom";
28343
- }
28344
- function resolveProviderTarget(input) {
28345
- const isCustom = !PROVIDER_DEFS[input.menuProvider];
28346
- const providerId = isCustom ? (input.customProviderId || "").trim() || "custom" : input.menuProvider;
28347
- const rawRef = (input.modelRef || "").trim();
28348
- const modelId = (rawRef.includes("/") ? modelIdOf(rawRef) : rawRef) || (input.modelId || "").trim();
28349
- return { providerId, modelId, modelRef: modelId ? `${providerId}/${modelId}` : "" };
28350
- }
28351
- function visionOf(ref, catalog) {
28352
- const hit = catalog?.find((m) => m.id === modelIdOf(ref));
28353
- if (hit && typeof hit.vision === "boolean") return hit.vision;
28354
- return VISION_CAPABLE[ref];
28355
- }
28356
- function builtinVisionModels(providerId) {
28357
- const def = PROVIDER_DEFS[providerId];
28358
- if (!def) return [];
28359
- return def.models.filter((m) => (m.input || []).includes("image")).map((m) => `${providerId}/${m.id}`);
28360
- }
28361
- function resolveVisionModel(providerId, primaryModel, catalog) {
28362
- if (visionOf(primaryModel, catalog) !== false) return primaryModel;
28363
- const candidates = catalog?.length ? catalog.filter((m) => m.vision === true).map((m) => `${providerId}/${m.id}`) : builtinVisionModels(providerId);
28364
- return candidates[0] || "";
28365
- }
28366
- var TEMPLATE_VALUES = {
28367
- stateDir: "{{STATE_DIR}}",
28368
- timezone: "{{TIMEZONE}}",
28369
- agentName: "{{AGENT_NAME}}",
28370
- persona: "assistant",
28371
- chatMode: "auto",
28372
- primaryProvider: "dashscope",
28373
- zhipuPlan: "",
28374
- dashscopePlan: "",
28375
- providerModels: null,
28376
- // 走内置模型表,模板里展示内置形状
28377
- primaryApiKey: "",
28378
- // generateConfig 只写 env: 占位符,明文不落 config
28379
- primaryModel: "dashscope/qwen3.8-max",
28380
- // 派生而非手写:qwen3.8-max 不支持图片,vision 落到 flash。手写就会再制造一次
28381
- // "vision 指向一个 input 里没有 image 的型号"(见 VISION_CAPABLE 注释)。
28382
- visionModel: resolveVisionModel("dashscope", "dashscope/qwen3.8-max"),
28383
- discordEnabled: false,
28384
- discordToken: "",
28385
- discordUserId: "",
28386
- feishuEnabled: true,
28387
- feishuAppId: "{{FEISHU_APP_ID}}",
28388
- feishuAppSecret: "{{FEISHU_APP_SECRET}}",
28389
- feishuOpenId: "",
28390
- wechatEnabled: false,
28391
- // 微信块是条件写入的,模板只展示通用形状
28392
- wechatToken: "",
28393
- wechatAccountId: "",
28394
- customBaseUrl: "",
28395
- customModelId: "",
28396
- customApi: "anthropic",
28397
- customProviderId: "",
28398
- // 用 env: 而不是 {{}}:{{}} 是脚手架占位符({{STATE_DIR}} 那种,由工具替换),
28399
- // 而这一格真实 init 写的就是个 key。写成 {{TAVILY_API_KEY}} 会误导人以为有人会替换它。
28400
- // env:TAVILY_API_KEY 是引擎真认的形式(loader 会解析),照抄也能跑通。
28401
- tavilyKey: "env:TAVILY_API_KEY",
28402
- apiPort: 16990
28403
- };
28404
- function generateConfig(v) {
28405
- const workspace = path2.join(v.stateDir, "workspace").replace(/\\/g, "/");
28406
- const config = {
28407
- schemaVersion: SCHEMA_VERSION,
28408
- // 全引擎统一时区(design §3.1)。真实 init 走 getDefaultValues() 探测本机,
28409
- // 模板走 {{TIMEZONE}} 占位符。
28410
- timezone: v.timezone,
28411
- stateDir: v.stateDir.replace(/\\/g, "/"),
28412
- mediaDir: `${v.stateDir.replace(/\\/g, "/")}/media/inbound`,
28413
- models: {
28414
- providers: {}
28415
- },
28416
- // 0917:my_eyes(看图工具)读的是 tools.my_eyes.model,不是 tools.vision——
28417
- // 后者是模板里遗留的死键(全仓零读者),已删。visionModel 解析不出就不写这个块。
28418
- tools: {
28419
- ...v.tavilyKey ? { tavily: { apiKey: v.tavilyKey } } : {},
28420
- ...v.visionModel ? { my_eyes: { model: v.visionModel } } : {},
28421
- // 自拍(§4.3):免费档必须显式写 provider:'relay'。不写的话 getProvider()
28422
- // 会落回默认值 'fal',而免费装机没有 fal key——第一次自拍就失败。
28423
- // 非免费档只在用户真给了形象时才写这个块,且不替他决定 provider。
28424
- ...isFreeTier(v) || v.selfiePhotoRelPath || v.selfiePortraitDesc ? {
28425
- my_selfie: {
28426
- ...isFreeTier(v) ? { provider: "relay" } : {},
28427
- ...v.selfiePhotoRelPath ? { references: [{ name: "portrait", path: v.selfiePhotoRelPath }] } : {},
28428
- // 只在没有照片时才写描述——两个都给的话以照片为准,写进去会让人以为
28429
- // 描述也生效了(冷启读的就是这个键)
28430
- ...!v.selfiePhotoRelPath && v.selfiePortraitDesc ? { portraitDesc: v.selfiePortraitDesc } : {}
28431
- }
28432
- } : {}
28433
- },
28434
- session: { dmScope: "main", groupScope: "main" },
28435
- sandbox: { mode: "off" },
28436
- display: {
28437
- thinking: { enabled: false, emoji: "\u{1F4AD}", maxLen: 300 },
28438
- toolUse: { enabled: true, emoji: "\u{1F527}", displayMode: "raw", bashDisplayMode: "both" },
28439
- toolResult: { enabled: false },
28440
- reactions: { enabled: true, start: "\u{1F440}", done: "\u2705", error: "\u274C" },
28441
- preview: { enabled: true, agentName: v.agentName, color: "orange" },
28442
- typing: { enabled: true }
28443
- },
28444
- agents: {
28445
- defaults: {
28446
- workspace,
28447
- name: v.agentName,
28448
- // 人设落 config 是为了**复跑能读回来**(loadExistingValues)。引擎运行时不读它
28449
- // ——SOUL.md 已经按人设渲染好了。不写的话复跑 init 会退回 'assistant',
28450
- // 而 execute() 用模板重写 SOUL.md,用户选的"情感陪伴"重装一次就变回通用助手。
28451
- persona: v.persona,
28452
- model: {
28453
- primary: v.primaryModel,
28454
- // 条件写入:vision 引用解析不出时省略键(loader 按 if (visionRef) 处理缺失)
28455
- ...v.visionModel ? { vision: v.visionModel } : {}
28456
- },
28457
- features: {
28458
- filesystem: true,
28459
- shell: true,
28460
- webSearch: true,
28461
- webFetch: true,
28462
- memory: true,
28463
- topics: true,
28464
- "topic-extract": true,
28465
- "topic-recall": true,
28466
- cron: true,
28467
- agent: true,
28468
- agentTeams: true,
28469
- processOutput: "minimal",
28470
- interrupt: "command",
28471
- debounceMs: 5e3,
28472
- // 免费档的装机目标是「能对话、能看图、能自拍」,这两个 flag 默认 false
28473
- // (src/config/features.ts)。显式打开,它们是声明这两件事的地方。
28474
- ...isFreeTier(v) ? { selfie: true, eyes: true } : {}
28475
- }
28476
- }
28477
- },
28478
- channels: {
28479
- discord: {
28480
- enabled: v.discordEnabled,
28481
- dmPolicy: "pairing",
28482
- groupPolicy: "open",
28483
- accounts: { default: { token: v.discordToken } },
28484
- replyToMode: "all"
28485
- },
28486
- feishu: {
28487
- enabled: v.feishuEnabled,
28488
- appId: v.feishuAppId,
28489
- appSecret: v.feishuAppSecret,
28490
- connectionMode: "websocket",
28491
- dmPolicy: "pairing",
28492
- groupPolicy: "open"
28493
- },
28494
- ...v.wechatEnabled && v.wechatToken ? {
28495
- wechat: {
28496
- enabled: true,
28497
- token: v.wechatToken,
28498
- accountId: v.wechatAccountId,
28499
- dmPolicy: "pairing",
28500
- group: { policy: "disabled" }
28501
- }
28502
- } : {},
28503
- // 0920 翀哥拍板:emotion 通道默认开启(含 liveSystemPrompt 加载 EMOTION.md)
28504
- // 即使没装 EMOTION.md,buildLightStablePrompt 也会硬编码"想 TA 就发"那段兜底
28505
- emotion: {
28506
- enabled: true,
28507
- liveSystemPrompt: {
28508
- files: ["SOUL.md", "EMOTION.md"]
28509
- }
28510
- }
28511
- },
28512
- api: { port: v.apiPort },
28513
- // 0909 npm 包默认开:inner-voice + proactive 是开箱即用的核心体验
28514
- // (不写 model → 都兜底主模型;proactive 领域/persona 可用 workspace/prompts/ 覆盖)
28515
- innerVoice: { enabled: true },
28516
- proactive: { enabled: true },
28517
- prompt: {
28518
- mode: "custom",
28519
- staticFiles: ["AGENTS.md", "USER.md", "MEMORY.md", "memory/distill-output.md"],
28520
- order: ["soul", "AGENTS.md", "prompts/contacts.md", "using-tools", "USER.md", "MEMORY.md", "memory/distill-output.md", "auto-memory-instructions"]
28521
- },
28522
- compaction: {
28523
- enabled: true,
28524
- bufferTokens: 23e3,
28525
- maxOutputTokens: 16384,
28526
- minReductionRatio: 0.3,
28527
- ruleBased: { enabled: true, essentialFields: [] },
28528
- memoryFlush: { enabled: true, forceFlushTranscriptBytes: "2.0mb" }
28529
- },
28530
- skills: {
28531
- enabled: true,
28532
- path: "skills"
28533
- }
28534
- };
28535
- if (!PROVIDER_DEFS[v.primaryProvider]) {
28536
- const models = v.existingModels?.length ? [...v.existingModels] : [];
28537
- const customModelId = v.customModelId || models[0]?.id || modelIdOf(v.primaryModel);
28538
- if (!customModelId) {
28539
- throw new Error(
28540
- `\u81EA\u5B9A\u4E49 provider "${v.primaryProvider}" \u6CA1\u6709\u6A21\u578B id\u2014\u2014init \u751F\u6210\u4E0D\u51FA\u53EF\u7528\u7684 config\u3002\u8BF7\u5728\u5411\u5BFC\u91CC\u586B\u300C\u6A21\u578B ID\u300D\uFF0C\u6216\u8865\u4E0A configs/main7.json \u91CC models.providers.${v.primaryProvider}.models[0].id\u3002`
28541
- );
28811
+ });
28812
+ if (!createRes.ok) {
28813
+ const err = await createRes.text();
28814
+ throw new Error(`\u521B\u5EFA release \u5931\u8D25: ${createRes.status} ${err}`);
28542
28815
  }
28543
- if (models.length && !models.some((m) => m?.id === customModelId)) {
28544
- models.unshift({ id: customModelId, name: customModelId, reasoning: false, input: CUSTOM_DEFAULT_INPUT, contextWindow: 2e5, maxTokens: 8192 });
28816
+ const release2 = await createRes.json();
28817
+ const uploadUrl = release2.upload_url.replace("{?name,label}", "");
28818
+ const fileBuffer = fs31.readFileSync(archiveFile);
28819
+ const fileName = path33.basename(archiveFile);
28820
+ const uploadRes = await fetch(`${uploadUrl}?name=${fileName}`, {
28821
+ method: "POST",
28822
+ headers: {
28823
+ "Authorization": `Bearer ${cfg.githubToken}`,
28824
+ "Accept": "application/vnd.github+json",
28825
+ "Content-Type": "application/gzip",
28826
+ "Content-Length": String(fileBuffer.length)
28827
+ },
28828
+ body: fileBuffer
28829
+ });
28830
+ if (!uploadRes.ok) {
28831
+ const err = await uploadRes.text();
28832
+ throw new Error(`\u4E0A\u4F20 asset \u5931\u8D25: ${uploadRes.status} ${err}`);
28545
28833
  }
28546
- config.models.providers[v.primaryProvider] = {
28547
- baseUrl: (v.customBaseUrl || "").trim(),
28548
- api: v.customApi,
28549
- apiKey: "env:" + v.primaryProvider.toUpperCase().replace(/[^A-Z0-9_]/g, "_") + "_API_KEY",
28550
- models: models.length ? models : [
28551
- { id: customModelId, name: customModelId, reasoning: false, input: CUSTOM_DEFAULT_INPUT, contextWindow: 2e5, maxTokens: 8192 }
28552
- ]
28553
- };
28834
+ console.log(`\u2705 \u4E0A\u4F20\u6210\u529F!`);
28835
+ console.log(` release: ${release2.html_url}`);
28836
+ } catch (e) {
28837
+ console.error(`\u274C GitHub \u4E0A\u4F20\u5931\u8D25: ${e.message}`);
28838
+ console.error(` archive \u4ECD\u5728: ${archiveFile}`);
28839
+ throw e;
28554
28840
  }
28555
- if (PROVIDER_DEFS[v.primaryProvider]) {
28556
- const def = { ...PROVIDER_DEFS[v.primaryProvider] };
28557
- if (v.primaryProvider === "zhipu" && v.zhipuPlan === "token") {
28558
- def.baseUrl = "https://open.bigmodel.cn/api/paas/v4";
28559
- }
28560
- if (v.primaryProvider === "dashscope" && v.dashscopePlan === "token") {
28561
- def.baseUrl = "https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1";
28562
- }
28563
- if (v.primaryProvider === "7yi" && (v.customBaseUrl || "").trim()) {
28564
- def.baseUrl = v.customBaseUrl.trim();
28565
- }
28566
- if (v.providerModels && v.providerModels.length > 0) {
28567
- def.models = v.providerModels.map((m) => ({
28568
- id: m.id,
28569
- name: m.name || m.id,
28570
- reasoning: m.reasoning ?? true,
28571
- // 没标 vision 的条目按「不支持」写 input(目录是权威),但 visionOf 那边对
28572
- // 「没标」仍按未知处理——若用户手输的型号恰好是主模型,vision 仍会宽复用它
28573
- input: m.vision ? ["text", "image"] : ["text"],
28574
- contextWindow: m.contextWindow ?? 2e5,
28575
- maxTokens: m.maxTokens ?? 8192
28576
- }));
28841
+ try {
28842
+ fs31.rmSync(path33.dirname(archiveFile), { recursive: true, force: true });
28843
+ } catch {
28844
+ }
28845
+ }
28846
+ async function downloadFromGitHub(cfg, agentName, version, destDir) {
28847
+ const repo = `${cfg.githubOwner}/${cfg.repoName}`;
28848
+ const tag = version || await getLatestReleaseTag(cfg, agentName);
28849
+ console.log(`\u2B07\uFE0F \u4E0B\u8F7D: ${repo} release ${tag}`);
28850
+ const res = await fetch(`https://api.github.com/repos/${repo}/releases/tags/${tag}`, {
28851
+ headers: {
28852
+ "Authorization": `Bearer ${cfg.githubToken}`,
28853
+ "Accept": "application/vnd.github+json"
28577
28854
  }
28578
- const primaryModelId2 = modelIdOf(v.primaryModel);
28579
- if (primaryModelId2 && !def.models.some((m) => m?.id === primaryModelId2)) {
28580
- const old = (v.existingModels || []).find((m) => m?.id === primaryModelId2);
28581
- def.models = [...def.models, old || { id: primaryModelId2, name: primaryModelId2 }];
28855
+ });
28856
+ if (!res.ok) {
28857
+ throw new Error(`\u83B7\u53D6 release \u5931\u8D25: ${res.status}`);
28858
+ }
28859
+ const release2 = await res.json();
28860
+ const asset = release2.assets?.find((a) => a.name.endsWith(".tar.gz") || a.name.endsWith(".zip"));
28861
+ if (!asset) {
28862
+ throw new Error(`release ${tag} \u6CA1\u6709 tar.gz/zip asset`);
28863
+ }
28864
+ const downloadRes = await fetch(asset.url, {
28865
+ headers: {
28866
+ "Authorization": `Bearer ${cfg.githubToken}`,
28867
+ "Accept": "application/octet-stream"
28582
28868
  }
28583
- config.models.providers[v.primaryProvider] = {
28584
- ...def,
28585
- apiKey: "env:" + v.primaryProvider.toUpperCase().replace(/[^A-Z0-9_]/g, "_") + "_API_KEY",
28586
- ...def.baseUrl ? { baseUrl: def.baseUrl.trim() } : {}
28587
- };
28869
+ });
28870
+ if (!downloadRes.ok) {
28871
+ throw new Error(`\u4E0B\u8F7D asset \u5931\u8D25: ${downloadRes.status}`);
28588
28872
  }
28589
- const providerKeys = Object.keys(config.models.providers);
28590
- if (providerKeys.length === 0) {
28591
- throw new Error("\u6CA1\u6709\u751F\u6210\u4EFB\u4F55 provider\u2014\u2014\u68C0\u67E5 init \u7684 provider \u9009\u62E9\u4E0E\u6A21\u578B ID\u3002");
28873
+ const buffer = Buffer.from(await downloadRes.arrayBuffer());
28874
+ const archiveFile = path33.join(destDir, asset.name);
28875
+ fs31.writeFileSync(archiveFile, buffer);
28876
+ return archiveFile;
28877
+ }
28878
+ async function getLatestReleaseTag(cfg, agentName) {
28879
+ const repo = `${cfg.githubOwner}/${cfg.repoName}`;
28880
+ const res = await fetch(`https://api.github.com/repos/${repo}/releases?per_page=30`, {
28881
+ headers: {
28882
+ "Authorization": `Bearer ${cfg.githubToken}`,
28883
+ "Accept": "application/vnd.github+json"
28884
+ }
28885
+ });
28886
+ if (!res.ok) {
28887
+ throw new Error(`\u83B7\u53D6 release \u5217\u8868\u5931\u8D25: ${res.status}`);
28592
28888
  }
28593
- const primaryRef = String(config.agents.defaults.model.primary);
28594
- const primaryProviderId = primaryRef.split("/")[0];
28595
- if (!config.models.providers[primaryProviderId]) {
28596
- throw new Error(
28597
- `\u751F\u6210\u7684 config \u4E0D\u81EA\u6D3D\uFF1Aagents.defaults.model.primary = "${primaryRef}"\uFF0C\u4F46 models.providers \u91CC\u53EA\u6709 ${providerKeys.join(", ")}\u2014\u2014\u5F15\u64CE\u542F\u52A8\u4F1A\u76F4\u63A5\u629B Provider not found\u3002\u8FD9\u662F init \u7684 bug\uFF0C\u8BF7\u8FDE\u540C\u8FD9\u6761\u6D88\u606F\u4E00\u8D77\u53CD\u9988\u3002`
28598
- );
28889
+ const releases = await res.json();
28890
+ const match = releases.find((r) => r.tag_name?.startsWith(`${agentName}-`));
28891
+ if (!match) {
28892
+ throw new Error(`\u6CA1\u6709\u627E\u5230 ${agentName} \u7684 release`);
28599
28893
  }
28600
- const primaryModelId = primaryRef.split("/").slice(1).join("/");
28601
- const listed = config.models.providers[primaryProviderId].models || [];
28602
- if (primaryModelId && !listed.some((m) => m?.id === primaryModelId)) {
28603
- throw new Error(
28604
- `\u751F\u6210\u7684 config \u4E0D\u81EA\u6D3D\uFF1A\u4E3B\u6A21\u578B "${primaryRef}" \u4E0D\u5728 ${primaryProviderId} \u7684\u6A21\u578B\u8868\u91CC\uFF08\u8868\u91CC\u662F ${listed.map((m) => m?.id).join(", ") || "\u7A7A"}\uFF09\u3002\u8FD9\u662F init \u7684 bug\uFF0C\u8BF7\u8FDE\u540C\u8FD9\u6761\u6D88\u606F\u4E00\u8D77\u53CD\u9988\u3002`
28605
- );
28894
+ return match.tag_name;
28895
+ }
28896
+ var CONFIG_PATH, PATH_PLACEHOLDERS, WORKSPACE_INCLUDE, WORKSPACE_OPTIONAL, WORKSPACE_EXCLUDE, TEXT_EXTENSIONS;
28897
+ var init_cli_travel = __esm({
28898
+ "src/cli-travel.ts"() {
28899
+ "use strict";
28900
+ init_clock();
28901
+ CONFIG_PATH = path33.join(os5.homedir(), ".engine7-travel.json");
28902
+ PATH_PLACEHOLDERS = [
28903
+ { placeholder: "{{WORKSPACE}}", getOriginal: (d) => d.workspace },
28904
+ { placeholder: "{{ENGINE_HOME}}", getOriginal: (d) => d.engineHome },
28905
+ { placeholder: "{{STATE_DIR}}", getOriginal: (d) => d.stateDir }
28906
+ ];
28907
+ WORKSPACE_INCLUDE = /* @__PURE__ */ new Set([
28908
+ // 核心文件
28909
+ "AGENTS.md",
28910
+ "SOUL.md",
28911
+ "MEMORY.md",
28912
+ "USER.md",
28913
+ "HEARTBEAT.md",
28914
+ "INDEX.md",
28915
+ "SESSION-STATE.md",
28916
+ // 核心目录
28917
+ "prompts",
28918
+ "topics",
28919
+ "memory",
28920
+ "inner-voice",
28921
+ "docs",
28922
+ "skills",
28923
+ "voice-chat",
28924
+ "scripts",
28925
+ "selfie",
28926
+ "moodboard",
28927
+ // 状态文件 + 目录
28928
+ ".calendar",
28929
+ "nudge-state.json"
28930
+ ]);
28931
+ WORKSPACE_OPTIONAL = /* @__PURE__ */ new Set([
28932
+ "images",
28933
+ "tools"
28934
+ ]);
28935
+ WORKSPACE_EXCLUDE = /* @__PURE__ */ new Set([
28936
+ "livestream",
28937
+ "content-library",
28938
+ "tmp",
28939
+ ".git",
28940
+ "node_modules",
28941
+ "memory_runs",
28942
+ "workspace",
28943
+ "prompt-archive",
28944
+ "aim-archive",
28945
+ "test-agent",
28946
+ "nul",
28947
+ "*.bak*",
28948
+ "*.bak-*",
28949
+ "~$*"
28950
+ ]);
28951
+ TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
28952
+ ".md",
28953
+ ".json",
28954
+ ".txt",
28955
+ ".js",
28956
+ ".ts",
28957
+ ".mjs",
28958
+ ".py",
28959
+ ".yaml",
28960
+ ".yml",
28961
+ ".cmd",
28962
+ ".bat",
28963
+ ".sh",
28964
+ ".csv",
28965
+ ".html",
28966
+ ".toml",
28967
+ ".list"
28968
+ // .everos 的 everos.toml/config.toml/ome.toml/everos-env.list
28969
+ ]);
28606
28970
  }
28607
- if (v.visionModel) {
28608
- const visionProvider = v.visionModel.split("/")[0];
28609
- if (!config.models.providers[visionProvider]) {
28610
- console.warn(
28611
- `[config] \u5FFD\u7565 vision \u5F15\u7528 "${v.visionModel}"\uFF1Aprovider "${visionProvider}" \u4E0D\u5728\u672C\u6B21\u751F\u6210\u7684 providers\uFF08${Object.keys(config.models.providers).join(", ") || "\u7A7A"}\uFF09\u91CC\u2014\u2014\u5199\u8FDB\u53BB\u4F1A\u8BA9 loader \u89E3\u6790\u5931\u8D25\u3001\u5165\u7AD9\u56FE\u7247\u88AB\u9759\u9ED8\u4E22\u5F03\u3002\u56FE\u7247\u8BC6\u522B\u672C\u6B21\u5173\u95ED\u3002`
28612
- );
28613
- delete config.agents.defaults.model.vision;
28614
- delete config.tools.my_eyes;
28615
- }
28971
+ });
28972
+
28973
+ // src/cli-init.ts
28974
+ import * as path34 from "node:path";
28975
+ import * as fs32 from "node:fs";
28976
+
28977
+ // src/cli-service.ts
28978
+ function parseServiceArgs(args) {
28979
+ const action = args[0] || "";
28980
+ let configPath = "";
28981
+ let cwd = process.cwd();
28982
+ let nodeExe = "";
28983
+ for (let i = 1; i < args.length; i++) {
28984
+ if (args[i] === "--config" && args[i + 1]) configPath = args[++i];
28985
+ else if (args[i] === "--cwd" && args[i + 1]) cwd = args[++i];
28986
+ else if (args[i] === "--node-exe" && args[i + 1]) nodeExe = args[++i];
28616
28987
  }
28617
- return config;
28988
+ return { action, configPath, cwd, nodeExe };
28989
+ }
28990
+ function buildServiceWrapper(opts) {
28991
+ const nodeExe = opts.nodeExe || process.execPath;
28992
+ return `@echo off\r
28993
+ cd /d "${opts.cwd}"\r
28994
+ "${nodeExe}" "${opts.cliMjs}" start --config "${opts.configPath}"\r
28995
+ `;
28618
28996
  }
28619
28997
 
28998
+ // src/cli-init.ts
28999
+ init_feishu_quick_register();
29000
+ init_wechat();
29001
+ init_qr_render();
29002
+ init_cli_events();
29003
+ import * as readline3 from "node:readline";
29004
+ import { fileURLToPath as fileURLToPath7 } from "node:url";
29005
+
28620
29006
  // src/cli-init-answers.ts
29007
+ init_clock();
29008
+ init_cli_init_config();
29009
+ import * as fs2 from "node:fs";
28621
29010
  var VALID_PROVIDERS = ["dashscope", "minimax", "zhipu", "deepseek", "7yi", "custom"];
28622
29011
  var VALID_CHANNEL_MODES = ["skip", "qr", "manual"];
28623
29012
  var VALID_PERSONAS = ["assistant", "companion", "butler", "friend"];
@@ -28737,6 +29126,7 @@ function buildConfigValuesFromAnswers(stateDir, a, existing = {}) {
28737
29126
  // src/cli-init.ts
28738
29127
  init_secrets_env();
28739
29128
  init_clock();
29129
+ init_cli_init_config();
28740
29130
  var defaultWarningListeners = process.listeners("warning");
28741
29131
  process.removeAllListeners("warning");
28742
29132
  process.on("warning", (warning) => {
@@ -28749,7 +29139,7 @@ process.on("warning", (warning) => {
28749
29139
  });
28750
29140
  var headlessEmit = null;
28751
29141
  var __filename2 = fileURLToPath7(import.meta.url);
28752
- var __dirname = path33.dirname(__filename2);
29142
+ var __dirname = path34.dirname(__filename2);
28753
29143
  function parseArgs() {
28754
29144
  const args = process.argv.slice(2);
28755
29145
  let stateDir = "";
@@ -28776,10 +29166,10 @@ function parseArgs() {
28776
29166
  }
28777
29167
  }
28778
29168
  if (!stateDir) {
28779
- stateDir = path33.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
29169
+ stateDir = path34.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
28780
29170
  console.log(` \u4F7F\u7528\u9ED8\u8BA4\u76EE\u5F55: ${stateDir}`);
28781
29171
  }
28782
- stateDir = path33.resolve(stateDir);
29172
+ stateDir = path34.resolve(stateDir);
28783
29173
  return { stateDir, quick, dryRun: dryRun2, force, answersPath };
28784
29174
  }
28785
29175
  function printHelp() {
@@ -28894,16 +29284,16 @@ function getDefaultValues(stateDir) {
28894
29284
  };
28895
29285
  }
28896
29286
  function loadExistingValues(stateDir) {
28897
- const configPath = path33.join(stateDir, "configs", "main7.json");
29287
+ const configPath = path34.join(stateDir, "configs", "main7.json");
28898
29288
  try {
28899
- if (!fs31.existsSync(configPath)) return {};
28900
- const j = JSON.parse(fs31.readFileSync(configPath, "utf8"));
29289
+ if (!fs32.existsSync(configPath)) return {};
29290
+ const j = JSON.parse(fs32.readFileSync(configPath, "utf8"));
28901
29291
  const out = {};
28902
29292
  if (typeof j.timezone === "string" && j.timezone) out.timezone = j.timezone;
28903
29293
  if (j.agents?.defaults?.name) out.agentName = j.agents.defaults.name;
28904
29294
  if (j.agents?.defaults?.persona) out.persona = j.agents.defaults.persona;
28905
29295
  try {
28906
- const wm = fs31.readFileSync(path33.join(stateDir, "workspace", ".work-mode"), "utf8").trim();
29296
+ const wm = fs32.readFileSync(path34.join(stateDir, "workspace", ".work-mode"), "utf8").trim();
28907
29297
  if (wm === "on") out.chatMode = "work";
28908
29298
  else if (wm === "off") out.chatMode = "emotion";
28909
29299
  } catch {
@@ -28957,15 +29347,15 @@ function loadExistingValues(stateDir) {
28957
29347
  }
28958
29348
  function readSecretsValue(stateDir, envName) {
28959
29349
  try {
28960
- const secretsDir = path33.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7-secrets");
29350
+ const secretsDir = path34.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7-secrets");
28961
29351
  const cfgBase = "main7";
28962
29352
  const candidates = [
28963
- path33.join(secretsDir, `${cfgBase}.env`),
28964
- path33.join(stateDir, "configs", `.env.${cfgBase}`)
29353
+ path34.join(secretsDir, `${cfgBase}.env`),
29354
+ path34.join(stateDir, "configs", `.env.${cfgBase}`)
28965
29355
  ];
28966
29356
  for (const p of candidates) {
28967
- if (fs31.existsSync(p)) {
28968
- for (const line of fs31.readFileSync(p, "utf8").split("\n")) {
29357
+ if (fs32.existsSync(p)) {
29358
+ for (const line of fs32.readFileSync(p, "utf8").split("\n")) {
28969
29359
  const eq = line.indexOf("=");
28970
29360
  if (eq > 0 && line.slice(0, eq).trim() === envName) {
28971
29361
  return line.slice(eq + 1).trim();
@@ -29198,29 +29588,29 @@ async function configureWechat(rl, v) {
29198
29588
  }
29199
29589
  function buildDirTree(v) {
29200
29590
  const d = v.stateDir;
29201
- const w = path33.join(d, "workspace");
29591
+ const w = path34.join(d, "workspace");
29202
29592
  return [
29203
29593
  // 目录
29204
- { path: path33.join(d, "configs"), type: "dir" },
29205
- { path: path33.join(d, "state", "agents", "main", "sessions"), type: "dir" },
29206
- { path: path33.join(w, "prompts"), type: "dir" },
29207
- { path: path33.join(w, "memory", "daily"), type: "dir" },
29208
- { path: path33.join(w, "docs", "research"), type: "dir" },
29209
- { path: path33.join(w, "docs", "todo"), type: "dir" },
29210
- { path: path33.join(w, "docs", "decisions"), type: "dir" },
29211
- { path: path33.join(w, "docs", "knowledge"), type: "dir" },
29212
- { path: path33.join(w, "docs", "sop"), type: "dir" },
29213
- { path: path33.join(d, "logs"), type: "dir" },
29214
- { path: path33.join(d, "media", "inbound"), type: "dir" },
29594
+ { path: path34.join(d, "configs"), type: "dir" },
29595
+ { path: path34.join(d, "state", "agents", "main", "sessions"), type: "dir" },
29596
+ { path: path34.join(w, "prompts"), type: "dir" },
29597
+ { path: path34.join(w, "memory", "daily"), type: "dir" },
29598
+ { path: path34.join(w, "docs", "research"), type: "dir" },
29599
+ { path: path34.join(w, "docs", "todo"), type: "dir" },
29600
+ { path: path34.join(w, "docs", "decisions"), type: "dir" },
29601
+ { path: path34.join(w, "docs", "knowledge"), type: "dir" },
29602
+ { path: path34.join(w, "docs", "sop"), type: "dir" },
29603
+ { path: path34.join(d, "logs"), type: "dir" },
29604
+ { path: path34.join(d, "media", "inbound"), type: "dir" },
29215
29605
  // workspace 文件
29216
29606
  {
29217
- path: path33.join(w, "SESSION-STATE.md"),
29607
+ path: path34.join(w, "SESSION-STATE.md"),
29218
29608
  type: "file",
29219
29609
  content: readTemplate("workspace/SESSION-STATE.md")
29220
29610
  },
29221
- { path: path33.join(w, "HEARTBEAT.md"), type: "file", content: readTemplate("workspace/HEARTBEAT.md") },
29611
+ { path: path34.join(w, "HEARTBEAT.md"), type: "file", content: readTemplate("workspace/HEARTBEAT.md") },
29222
29612
  {
29223
- path: path33.join(w, "SOUL.md"),
29613
+ path: path34.join(w, "SOUL.md"),
29224
29614
  type: "file",
29225
29615
  // 0917:SOUL 按人设取模板(templates/workspace/souls/<persona>.md),
29226
29616
  // 单一人设模板缺失时回退通用骨架(workspace/SOUL.md)
@@ -29232,12 +29622,12 @@ function buildDirTree(v) {
29232
29622
  }
29233
29623
  })()
29234
29624
  },
29235
- { path: path33.join(w, "AGENTS.md"), type: "file", content: readTemplate("workspace/AGENTS.md") },
29625
+ { path: path34.join(w, "AGENTS.md"), type: "file", content: readTemplate("workspace/AGENTS.md") },
29236
29626
  {
29237
29627
  // 0920 翀哥拍板:EMOTION.md 模板按人设装(templates/workspace/emotion/<persona>.md)
29238
29628
  // 冷启动默认带"想 TA 了就直接发(msg_send)"那段(写在每份模板 §一)
29239
29629
  // 单一人设模板缺失时回退通用骨架(emotion/_default.md)
29240
- path: path33.join(w, "EMOTION.md"),
29630
+ path: path34.join(w, "EMOTION.md"),
29241
29631
  type: "file",
29242
29632
  content: (() => {
29243
29633
  try {
@@ -29248,30 +29638,30 @@ function buildDirTree(v) {
29248
29638
  })()
29249
29639
  },
29250
29640
  {
29251
- path: path33.join(w, "prompts", "contacts.md"),
29641
+ path: path34.join(w, "prompts", "contacts.md"),
29252
29642
  type: "file",
29253
29643
  content: readTemplate("workspace/prompts/contacts.md").replace("{{DISCORD_USER_ID}}", v.discordUserId || "YOUR_DISCORD_ID").replace("{{FEISHU_OPEN_ID}}", v.feishuOpenId || "YOUR_FEISHU_OPEN_ID")
29254
29644
  },
29255
- { path: path33.join(w, "prompts", "auto-memory-instructions.md"), type: "file", content: readTemplate("workspace/prompts/auto-memory-instructions.md") },
29645
+ { path: path34.join(w, "prompts", "auto-memory-instructions.md"), type: "file", content: readTemplate("workspace/prompts/auto-memory-instructions.md") },
29256
29646
  {
29257
29647
  // 0917:inner-voice 定制 prompt 按人设装(templates/workspace/inner-voice/<persona>.md)
29258
- path: path33.join(w, "prompts", "my-inner-voice.md"),
29648
+ path: path34.join(w, "prompts", "my-inner-voice.md"),
29259
29649
  type: "file",
29260
29650
  content: readTemplate(`workspace/inner-voice/${v.persona}.md`).replace(/\{\{AGENT_NAME\}\}/g, v.agentName)
29261
29651
  },
29262
- { path: path33.join(w, "MEMORY.md"), type: "file", content: "# MEMORY.md \u2014 \u8BB0\u5FC6\u6587\u4EF6\u7D22\u5F15\n\n> \u6700\u540E\u66F4\u65B0\uFF1A\u521D\u59CB\u5316\n" },
29263
- { path: path33.join(w, "USER.md"), type: "file", content: "# USER.md \u2014 \u7528\u6237\u4FE1\u606F\n\n\uFF08\u5728\u8FD9\u91CC\u8BB0\u5F55\u7528\u6237\u7684\u504F\u597D\u3001\u80CC\u666F\u7B49\uFF09\n" },
29652
+ { path: path34.join(w, "MEMORY.md"), type: "file", content: "# MEMORY.md \u2014 \u8BB0\u5FC6\u6587\u4EF6\u7D22\u5F15\n\n> \u6700\u540E\u66F4\u65B0\uFF1A\u521D\u59CB\u5316\n" },
29653
+ { path: path34.join(w, "USER.md"), type: "file", content: "# USER.md \u2014 \u7528\u6237\u4FE1\u606F\n\n\uFF08\u5728\u8FD9\u91CC\u8BB0\u5F55\u7528\u6237\u7684\u504F\u597D\u3001\u80CC\u666F\u7B49\uFF09\n" },
29264
29654
  // 情绪状态机(0917):脚本 + hook 接线 + 人设情绪表(用户在 init 选的 persona)
29265
29655
  //
29266
29656
  // 0920 起装的是 **.mjs 版**(原来是 .py,见下面 hook 命令那段)。老装机的 workspace
29267
29657
  // 里那份 .py 不动它——没被引用就不会跑,删了反而无据可查。
29268
29658
  {
29269
- path: path33.join(w, "scripts", "emotion_state_machine.mjs"),
29659
+ path: path34.join(w, "scripts", "emotion_state_machine.mjs"),
29270
29660
  type: "file",
29271
29661
  content: readTemplate("workspace/scripts/emotion_state_machine.mjs")
29272
29662
  },
29273
29663
  {
29274
- path: path33.join(w, ".hooks.json"),
29664
+ path: path34.join(w, ".hooks.json"),
29275
29665
  type: "file",
29276
29666
  content: JSON.stringify({
29277
29667
  hooks: {
@@ -29286,26 +29676,26 @@ function buildDirTree(v) {
29286
29676
  // (hook 的 env 是 {...process.env} 继承的),所以它就是个 node。
29287
29677
  // 与 `service install --node-exe` 同一个路子。
29288
29678
  // 路径两侧都加引号:stateDir 可能带空格(C:\Users\Foo Bar\...)。
29289
- { hooks: [{ type: "command", command: '"${E7_NODE}" "' + path33.join(w, "scripts", "emotion_state_machine.mjs") + '"' }] }
29679
+ { hooks: [{ type: "command", command: '"${E7_NODE}" "' + path34.join(w, "scripts", "emotion_state_machine.mjs") + '"' }] }
29290
29680
  ]
29291
29681
  }
29292
29682
  }, null, 2) + "\n"
29293
29683
  },
29294
29684
  {
29295
- path: path33.join(w, ".emotion_states.json"),
29685
+ path: path34.join(w, ".emotion_states.json"),
29296
29686
  type: "file",
29297
29687
  content: readTemplate(`workspace/emotion-states/${v.persona}.json`)
29298
29688
  },
29299
29689
  // 模式档位(0920):与 /work 命令写的是同一个文件,读端只认 on/off,
29300
29690
  // 没有这个键就是 auto。auto 刻意不写——写个 "auto" 进去反而多一种除了这里没人认得的状态。
29301
29691
  ...v.chatMode === "work" || v.chatMode === "emotion" ? [{
29302
- path: path33.join(w, ".work-mode"),
29692
+ path: path34.join(w, ".work-mode"),
29303
29693
  type: "file",
29304
29694
  content: v.chatMode === "work" ? "on" : "off"
29305
29695
  }] : [],
29306
29696
  // package.json — 让 agent 目录成为独立 npm 项目根,防止 npm hoisting
29307
29697
  {
29308
- path: path33.join(d, "package.json"),
29698
+ path: path34.join(d, "package.json"),
29309
29699
  type: "file",
29310
29700
  content: JSON.stringify({
29311
29701
  name: v.agentName.toLowerCase().replace(/[^a-z0-9-]/g, "-") || "agent",
@@ -29316,7 +29706,7 @@ function buildDirTree(v) {
29316
29706
  },
29317
29707
  // 启动脚本(根据 OS 生成)
29318
29708
  ...process.platform === "win32" ? [{
29319
- path: path33.join(d, "start.cmd"),
29709
+ path: path34.join(d, "start.cmd"),
29320
29710
  type: "file",
29321
29711
  content: `@echo off
29322
29712
  rem Engine 7 startup script
@@ -29348,7 +29738,7 @@ if %ERRORLEVEL% NEQ 0 (
29348
29738
  )
29349
29739
  `
29350
29740
  }] : [{
29351
- path: path33.join(d, "start.sh"),
29741
+ path: path34.join(d, "start.sh"),
29352
29742
  type: "file",
29353
29743
  content: `#!/bin/bash
29354
29744
  # Engine 7 startup script
@@ -29381,24 +29771,24 @@ node "$ENGINE7_BIN" --engine-config configs/main7.json
29381
29771
  }
29382
29772
  function readTemplate(relativePath) {
29383
29773
  const candidates = [
29384
- path33.join(__dirname, "..", "templates", relativePath),
29385
- path33.join(__dirname, "..", "..", "templates", relativePath),
29386
- path33.join(process.cwd(), "templates", relativePath)
29774
+ path34.join(__dirname, "..", "templates", relativePath),
29775
+ path34.join(__dirname, "..", "..", "templates", relativePath),
29776
+ path34.join(process.cwd(), "templates", relativePath)
29387
29777
  ];
29388
29778
  for (const p of candidates) {
29389
- if (fs31.existsSync(p)) {
29390
- return fs31.readFileSync(p, "utf-8");
29779
+ if (fs32.existsSync(p)) {
29780
+ return fs32.readFileSync(p, "utf-8");
29391
29781
  }
29392
29782
  }
29393
29783
  throw new Error(`\u6A21\u677F\u6587\u4EF6\u4E0D\u5B58\u5728: ${relativePath}\uFF08\u627E\u4E86: ${candidates.join(", ")}\uFF09`);
29394
29784
  }
29395
29785
  function looksLikeAppInstall(dir) {
29396
- return ["", "install"].some((sub) => fs31.existsSync(path33.join(dir, sub, "Engine7.exe")) && fs31.existsSync(path33.join(dir, sub, "resources", "engine7", "dist", "cli.mjs")));
29786
+ return ["", "install"].some((sub) => fs32.existsSync(path34.join(dir, sub, "Engine7.exe")) && fs32.existsSync(path34.join(dir, sub, "resources", "engine7", "dist", "cli.mjs")));
29397
29787
  }
29398
29788
  var PHOTO_EXTS = [".png", ".jpg", ".jpeg", ".webp"];
29399
29789
  function hasOnlyEngineBookkeeping(dir) {
29400
29790
  try {
29401
- const entries2 = fs31.readdirSync(dir);
29791
+ const entries2 = fs32.readdirSync(dir);
29402
29792
  return entries2.length > 0 && entries2.every((e) => e === "install-identity.json");
29403
29793
  } catch {
29404
29794
  return false;
@@ -29414,11 +29804,11 @@ function dryRun(entries2) {
29414
29804
  function execute(entries2) {
29415
29805
  for (const e of entries2) {
29416
29806
  if (e.type === "dir") {
29417
- fs31.mkdirSync(e.path, { recursive: true });
29807
+ fs32.mkdirSync(e.path, { recursive: true });
29418
29808
  console.log(` \u{1F4C1} ${e.path}`);
29419
29809
  } else {
29420
- fs31.mkdirSync(path33.dirname(e.path), { recursive: true });
29421
- fs31.writeFileSync(e.path, e.content || "", "utf-8");
29810
+ fs32.mkdirSync(path34.dirname(e.path), { recursive: true });
29811
+ fs32.writeFileSync(e.path, e.content || "", "utf-8");
29422
29812
  console.log(` \u{1F4C4} ${e.path}`);
29423
29813
  }
29424
29814
  }
@@ -29452,8 +29842,8 @@ async function main() {
29452
29842
  if (subcommand === "--version" || subcommand === "-v") {
29453
29843
  let ver = "unknown";
29454
29844
  try {
29455
- const pkgPath = path33.join(path33.dirname(__filename2), "..", "package.json");
29456
- ver = JSON.parse(fs31.readFileSync(pkgPath, "utf8")).version || "unknown";
29845
+ const pkgPath = path34.join(path34.dirname(__filename2), "..", "package.json");
29846
+ ver = JSON.parse(fs32.readFileSync(pkgPath, "utf8")).version || "unknown";
29457
29847
  } catch {
29458
29848
  }
29459
29849
  console.log(`engine7 ${ver}`);
@@ -29478,14 +29868,14 @@ async function main() {
29478
29868
  relay = resolveRelayUrl(relay, process.env);
29479
29869
  if (!relayFromArg) console.error(` \u4E2D\u8F6C\u7AD9: ${relay}\uFF08\u8981\u6362\u5730\u5740\u7528 --relay <\u5730\u5740>\uFF09`);
29480
29870
  const { resolveStateDirFromArgs: resolveStateDirFromArgs2 } = await Promise.resolve().then(() => (init_cli_doctor(), cli_doctor_exports));
29481
- stateDir = stateDir ? path33.resolve(stateDir) : resolveStateDirFromArgs2(args.slice(1));
29871
+ stateDir = stateDir ? path34.resolve(stateDir) : resolveStateDirFromArgs2(args.slice(1));
29482
29872
  try {
29483
29873
  const { provisionFreeToken: provisionFreeToken2 } = await Promise.resolve().then(() => (init_provision_free(), provision_free_exports));
29484
29874
  const result = await provisionFreeToken2({
29485
29875
  relayBaseUrl: relay,
29486
29876
  stateDir,
29487
29877
  // 和 init 写 config 的路径一致(cli-init.ts 里 configPath 就是这么拼的)
29488
- configPath: path33.join(stateDir, "configs", "main7.json")
29878
+ configPath: path34.join(stateDir, "configs", "main7.json")
29489
29879
  });
29490
29880
  console.log(JSON.stringify(result, null, 2));
29491
29881
  process.exit(0);
@@ -29502,24 +29892,28 @@ async function main() {
29502
29892
  let stateDir = "";
29503
29893
  let configName = "";
29504
29894
  let events = false;
29895
+ let free = false;
29896
+ let relayBaseUrl = "";
29505
29897
  for (let i = 1; i < args.length; i++) {
29506
29898
  if (args[i] === "--state-dir" && args[i + 1]) stateDir = args[++i].trim();
29507
29899
  else if (args[i] === "--config" && args[i + 1]) configName = args[++i].trim();
29508
29900
  else if (args[i] === "--events") events = true;
29901
+ else if (args[i] === "--free") free = true;
29902
+ else if (args[i] === "--base-url" && args[i + 1]) relayBaseUrl = args[++i].trim();
29509
29903
  }
29510
29904
  if (!stateDir) {
29511
- const cwd = path33.resolve(process.cwd());
29512
- if (fs31.existsSync(path33.join(cwd, "configs"))) {
29905
+ const cwd = path34.resolve(process.cwd());
29906
+ if (fs32.existsSync(path34.join(cwd, "configs"))) {
29513
29907
  stateDir = cwd;
29514
29908
  } else {
29515
- const pointer = path33.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7.json");
29516
- if (fs31.existsSync(pointer)) {
29909
+ const pointer = path34.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7.json");
29910
+ if (fs32.existsSync(pointer)) {
29517
29911
  try {
29518
- stateDir = JSON.parse(fs31.readFileSync(pointer, "utf8")).stateDir || "";
29912
+ stateDir = JSON.parse(fs32.readFileSync(pointer, "utf8")).stateDir || "";
29519
29913
  } catch {
29520
29914
  }
29521
29915
  }
29522
- if (!stateDir) stateDir = path33.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
29916
+ if (!stateDir) stateDir = path34.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
29523
29917
  }
29524
29918
  }
29525
29919
  if (!target && !isLegacyAddchannel) {
@@ -29531,7 +29925,7 @@ async function main() {
29531
29925
  console.log(" feishu \u98DE\u4E66\u626B\u7801\u63A5\u5165\uFF08OAuth\uFF0C30\u79D2\uFF09");
29532
29926
  console.log(" discord Discord bot \u914D\u7F6E");
29533
29927
  console.log(" \u6A21\u578B\u7C7B:");
29534
- console.log(" llm \u66F4\u6362 LLM provider / API key / \u7AEF\u70B9");
29928
+ console.log(" llm --free \u5207\u5230 7yi \u514D\u8D39\u6863\uFF08\u4E0D\u7528\u586B Key\uFF1B\u8001\u7528\u6237\u4ECE\u65E7\u914D\u7F6E\u5347\u4E0A\u6765\u7684\u90A3\u6761\u8DEF\uFF09");
29535
29929
  console.log(" \u5176\u4ED6:");
29536
29930
  console.log(" channels \u67E5\u770B\u5F53\u524D\u901A\u9053\u72B6\u6001");
29537
29931
  console.log("");
@@ -29539,12 +29933,12 @@ async function main() {
29539
29933
  process.exit(0);
29540
29934
  }
29541
29935
  if (channel === "channels") {
29542
- const configsDir = path33.join(stateDir, "configs");
29543
- if (fs31.existsSync(configsDir)) {
29544
- const files = fs31.readdirSync(configsDir).filter((f) => f.endsWith(".json"));
29936
+ const configsDir = path34.join(stateDir, "configs");
29937
+ if (fs32.existsSync(configsDir)) {
29938
+ const files = fs32.readdirSync(configsDir).filter((f) => f.endsWith(".json"));
29545
29939
  for (const f of files) {
29546
29940
  try {
29547
- const j = JSON.parse(fs31.readFileSync(path33.join(configsDir, f), "utf8"));
29941
+ const j = JSON.parse(fs32.readFileSync(path34.join(configsDir, f), "utf8"));
29548
29942
  if (!j.channels) continue;
29549
29943
  console.log(`
29550
29944
  \u{1F4C4} ${f}:`);
@@ -29561,8 +29955,51 @@ async function main() {
29561
29955
  process.exit(0);
29562
29956
  }
29563
29957
  if (channel === "llm") {
29564
- console.log("\u{1F527} LLM \u91CD\u914D\uFF08\u5F00\u53D1\u4E2D\uFF0Cv7.2 \u8BA1\u5212\uFF09");
29565
- console.log(" \u76EE\u524D\u8BF7\u624B\u52A8\u6539 config \u7684 models.providers \u6BB5");
29958
+ let fail2 = function(message) {
29959
+ if (emit2) emit2({ event: "error", message });
29960
+ else console.error(`
29961
+ \u274C ${message}`);
29962
+ process.exit(1);
29963
+ };
29964
+ var fail = fail2;
29965
+ const { applyFreeTierToConfig: applyFreeTierToConfig2, resolveConfigPath: resolveConfigPath2 } = await Promise.resolve().then(() => (init_free_tier_apply(), free_tier_apply_exports));
29966
+ const { FREE_TIER_ENV_KEY: FREE_TIER_ENV_KEY2 } = await Promise.resolve().then(() => (init_provision_free(), provision_free_exports));
29967
+ const emit2 = events ? createEmitter() : void 0;
29968
+ if (events) redirectConsoleToStderr();
29969
+ if (!free) {
29970
+ if (emit2) fail2("reconfig llm \u9700\u8981 --free\uFF08\u5207\u5230 7yi \u514D\u8D39\u6863\uFF09");
29971
+ console.log("\u{1F527} LLM \u91CD\u914D");
29972
+ console.log(" \u5F53\u524D\u652F\u6301: engine7 reconfig llm --free \u5207\u5230 7yi \u514D\u8D39\u6863\uFF08\u4E0D\u7528\u586B Key\uFF09");
29973
+ console.log(" \u6362\u5230\u522B\u7684\u5382\u5546\u4ECD\u8D70\u5B89\u88C5\u7A0B\u5E8F / \u547D\u4EE4\u884C init\uFF08\u901A\u7528\u6362\u5382\u5546 v7.2 \u8BA1\u5212\u505A\uFF09");
29974
+ process.exit(0);
29975
+ }
29976
+ const configFile = resolveConfigPath2(stateDir, configName);
29977
+ if (!configFile) {
29978
+ fail2(`\u6CA1\u627E\u5230\u53EF\u6539\u7684 config\uFF1A\u5728 ${path34.join(stateDir, "configs")} \u4E0B\u65E2\u6CA1\u6709 main7.json\uFF0C\u4E5F\u6CA1\u6709\u542B models.providers \u7684 json\u3002\u7528 --config <\u6587\u4EF6\u540D> \u6307\u5B9A\u3002`);
29979
+ }
29980
+ if (!readSecretsValue(stateDir, FREE_TIER_ENV_KEY2)) {
29981
+ fail2(`\u514D\u8D39\u6863\u7F3A\u5C11\u4EE4\u724C\uFF1Asecrets \u91CC\u6CA1\u6709 ${FREE_TIER_ENV_KEY2}\u3002\u5148\u9886\u53F7\uFF1A
29982
+ engine7 provision-free --relay <\u4E2D\u8F6C\u7AD9\u5730\u5740> --state-dir ${stateDir}`);
29983
+ }
29984
+ if (emit2) emit2({ event: "progress", step: "write_config" });
29985
+ let applied;
29986
+ try {
29987
+ applied = applyFreeTierToConfig2(configFile, { baseUrl: relayBaseUrl });
29988
+ } catch (e) {
29989
+ fail2(e?.message || String(e));
29990
+ }
29991
+ if (emit2) {
29992
+ emit2({ event: "done", channel: "llm", detail: applied.primary, restart_required: true });
29993
+ } else {
29994
+ console.log(`
29995
+ \u2705 \u5DF2\u5207\u5230 7yi \u514D\u8D39\u6863: ${applied.configPath}`);
29996
+ console.log(` \u4E3B\u6A21\u578B ${applied.primary}${applied.vision ? `\uFF0C\u89C6\u89C9 ${applied.vision}` : ""}`);
29997
+ console.log(` \u539F\u914D\u7F6E\u5DF2\u5907\u4EFD: ${applied.backupPath}`);
29998
+ console.log("\n\u26A0\uFE0F \u5FC5\u987B\u91CD\u542F\u5F15\u64CE\u624D\u751F\u6548\u2014\u2014secrets \u7684\u76D1\u89C6\u5668\u53EA\u5728\u300C\u542F\u52A8\u65F6\u90A3\u4E2A\u6587\u4EF6\u5DF2\u5B58\u5728\u300D\u65F6\u624D\u5EFA");
29999
+ console.log(" \uFF08src/reload/reload.ts \u7684 secrets-watch\uFF09\uFF0C\u521A\u5199\u5B8C\u7684\u4EE4\u724C\u70ED\u52A0\u8F7D\u63A5\u4E0D\u5230\uFF1B");
30000
+ console.log(" \u4E0D\u91CD\u542F\u5F15\u64CE\u4F1A\u628A\u5B57\u9762\u91CF env:7YI_API_KEY \u5F53 key \u53D1\u51FA\u53BB \u2192 401\u3002");
30001
+ console.log(" engine7 restart");
30002
+ }
29566
30003
  process.exit(0);
29567
30004
  }
29568
30005
  if (channel === "discord") {
@@ -29605,8 +30042,8 @@ async function main() {
29605
30042
  else if (args[i] === "--note" && args[i + 1]) note = args[++i];
29606
30043
  else if (args[i] === "--dry-run") dryRun2 = true;
29607
30044
  }
29608
- if (!exportStateDir) exportStateDir = path33.resolve(process.cwd());
29609
- if (!agentName) agentName = path33.basename(exportStateDir);
30045
+ if (!exportStateDir) exportStateDir = path34.resolve(process.cwd());
30046
+ if (!agentName) agentName = path34.basename(exportStateDir);
29610
30047
  await doExport2({ agentName, stateDir: exportStateDir, note, dryRun: dryRun2 });
29611
30048
  process.exit(0);
29612
30049
  }
@@ -29628,7 +30065,7 @@ async function main() {
29628
30065
  console.error("\u274C import \u9700\u8981 --state-dir <path>");
29629
30066
  process.exit(1);
29630
30067
  }
29631
- if (!agentName) agentName = path33.basename(importStateDir);
30068
+ if (!agentName) agentName = path34.basename(importStateDir);
29632
30069
  await doImport2({ agentName, stateDir: importStateDir, version, archiveFile: archiveFile || void 0, dryRun: dryRun2 });
29633
30070
  process.exit(0);
29634
30071
  }
@@ -29653,18 +30090,18 @@ engine7 start \u2014 \u542F\u52A8 Engine
29653
30090
  }
29654
30091
  }
29655
30092
  if (!configPath2) {
29656
- const defaultCfg = path33.join("configs", "main7.json");
29657
- const homeCfg = path33.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7", "configs", "main7.json");
29658
- if (fs31.existsSync(defaultCfg)) {
30093
+ const defaultCfg = path34.join("configs", "main7.json");
30094
+ const homeCfg = path34.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7", "configs", "main7.json");
30095
+ if (fs32.existsSync(defaultCfg)) {
29659
30096
  configPath2 = defaultCfg;
29660
- } else if (fs31.existsSync(homeCfg)) {
30097
+ } else if (fs32.existsSync(homeCfg)) {
29661
30098
  configPath2 = homeCfg;
29662
30099
  console.log(` \u4F7F\u7528\u9ED8\u8BA4 config: ${homeCfg}`);
29663
30100
  } else {
29664
30101
  const ptr = readHomePointer();
29665
30102
  if (ptr?.stateDir) {
29666
- const ptrCfg = path33.join(ptr.stateDir, "configs", "main7.json");
29667
- if (fs31.existsSync(ptrCfg)) {
30103
+ const ptrCfg = path34.join(ptr.stateDir, "configs", "main7.json");
30104
+ if (fs32.existsSync(ptrCfg)) {
29668
30105
  configPath2 = ptrCfg;
29669
30106
  console.log(` \u4F7F\u7528 config: ${ptrCfg}`);
29670
30107
  }
@@ -29679,19 +30116,19 @@ engine7 start \u2014 \u542F\u52A8 Engine
29679
30116
  }
29680
30117
  const { execSync: execSync3, spawn: spawn5 } = await import("node:child_process");
29681
30118
  let enginePath;
29682
- const localPath = path33.join("node_modules", "engine7", "dist", "main.mjs");
29683
- if (fs31.existsSync(localPath)) {
30119
+ const localPath = path34.join("node_modules", "engine7", "dist", "main.mjs");
30120
+ if (fs32.existsSync(localPath)) {
29684
30121
  enginePath = localPath;
29685
30122
  } else {
29686
- const cliPath = path33.resolve(process.argv[1]);
29687
- const distDir = path33.dirname(cliPath);
29688
- const candidate = path33.join(distDir, "main.mjs");
29689
- if (fs31.existsSync(candidate)) {
30123
+ const cliPath = path34.resolve(process.argv[1]);
30124
+ const distDir = path34.dirname(cliPath);
30125
+ const candidate = path34.join(distDir, "main.mjs");
30126
+ if (fs32.existsSync(candidate)) {
29690
30127
  enginePath = candidate;
29691
30128
  } else {
29692
30129
  try {
29693
30130
  const globalRoot = execSync3("npm root -g", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
29694
- enginePath = path33.join(globalRoot, "engine7", "dist", "main.mjs");
30131
+ enginePath = path34.join(globalRoot, "engine7", "dist", "main.mjs");
29695
30132
  } catch {
29696
30133
  console.error("\u274C \u627E\u4E0D\u5230 engine7 main.mjs");
29697
30134
  process.exit(1);
@@ -29701,7 +30138,7 @@ engine7 start \u2014 \u542F\u52A8 Engine
29701
30138
  console.log(label);
29702
30139
  console.log(` config: ${configPath2}`);
29703
30140
  console.log(` engine: ${enginePath}`);
29704
- const configName = path33.basename(configPath2);
30141
+ const configName = path34.basename(configPath2);
29705
30142
  const myPid = process.pid;
29706
30143
  try {
29707
30144
  if (process.platform === "win32") {
@@ -29719,16 +30156,16 @@ engine7 start \u2014 \u542F\u52A8 Engine
29719
30156
  }
29720
30157
  console.log("");
29721
30158
  const envForChild = { ...process.env };
29722
- const secretsDir = path33.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7-secrets");
29723
- const cfgBase = path33.basename(configPath2, ".json");
30159
+ const secretsDir = path34.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7-secrets");
30160
+ const cfgBase = path34.basename(configPath2, ".json");
29724
30161
  const secretCandidates = [
29725
- path33.join(secretsDir, `${cfgBase}.env`),
29726
- path33.join(path33.dirname(configPath2), `.env.${cfgBase}`),
29727
- path33.join(path33.dirname(configPath2), ".env")
30162
+ path34.join(secretsDir, `${cfgBase}.env`),
30163
+ path34.join(path34.dirname(configPath2), `.env.${cfgBase}`),
30164
+ path34.join(path34.dirname(configPath2), ".env")
29728
30165
  ];
29729
30166
  for (const secretPath of secretCandidates) {
29730
- if (fs31.existsSync(secretPath)) {
29731
- const content = fs31.readFileSync(secretPath, "utf-8");
30167
+ if (fs32.existsSync(secretPath)) {
30168
+ const content = fs32.readFileSync(secretPath, "utf-8");
29732
30169
  for (const line of content.split("\n")) {
29733
30170
  const trimmed = line.trim();
29734
30171
  if (!trimmed || trimmed.startsWith("#")) continue;
@@ -29803,14 +30240,14 @@ engine7 session \u2014 \u4F1A\u8BDD\u7BA1\u7406\u5DE5\u5177
29803
30240
  console.error(`\u274C \u672A\u77E5\u6A21\u5F0F: ${mode}\uFF08\u53EF\u9009: archive / drop-last / strip-images\uFF09`);
29804
30241
  process.exit(1);
29805
30242
  }
29806
- const configRaw = JSON.parse(fs31.readFileSync(configPath2, "utf-8"));
30243
+ const configRaw = JSON.parse(fs32.readFileSync(configPath2, "utf-8"));
29807
30244
  const stateDir = configRaw.stateDir;
29808
30245
  if (!stateDir) {
29809
30246
  console.error("\u274C config \u91CC\u6CA1\u6709 stateDir");
29810
30247
  process.exit(1);
29811
30248
  }
29812
- const sessionsDir = path33.join(stateDir, "agents", "main", "sessions");
29813
- if (!fs31.existsSync(sessionsDir)) {
30249
+ const sessionsDir = path34.join(stateDir, "agents", "main", "sessions");
30250
+ if (!fs32.existsSync(sessionsDir)) {
29814
30251
  console.error(`\u274C sessions \u76EE\u5F55\u4E0D\u5B58\u5728: ${sessionsDir}`);
29815
30252
  process.exit(1);
29816
30253
  }
@@ -29818,37 +30255,37 @@ engine7 session \u2014 \u4F1A\u8BDD\u7BA1\u7406\u5DE5\u5177
29818
30255
  console.log(` config: ${configPath2}`);
29819
30256
  console.log(` mode: ${mode}`);
29820
30257
  if (mode === "drop-last") console.log(` n: ${dropN}`);
29821
- const platformMapPath = path33.join(sessionsDir, "platform-map.json");
29822
- const indexPath = path33.join(sessionsDir, "session-index.json");
30258
+ const platformMapPath = path34.join(sessionsDir, "platform-map.json");
30259
+ const indexPath = path34.join(sessionsDir, "session-index.json");
29823
30260
  let sessionFileUUID = null;
29824
- if (fs31.existsSync(indexPath)) {
29825
- const index = JSON.parse(fs31.readFileSync(indexPath, "utf-8"));
30261
+ if (fs32.existsSync(indexPath)) {
30262
+ const index = JSON.parse(fs32.readFileSync(indexPath, "utf-8"));
29826
30263
  const entries2 = Object.values(index);
29827
30264
  if (entries2.length > 0) {
29828
30265
  const fileVal = entries2[0].file || "";
29829
- sessionFileUUID = path33.isAbsolute(fileVal) ? path33.basename(fileVal, ".jsonl") : fileVal.replace(".jsonl", "");
30266
+ sessionFileUUID = path34.isAbsolute(fileVal) ? path34.basename(fileVal, ".jsonl") : fileVal.replace(".jsonl", "");
29830
30267
  }
29831
30268
  }
29832
30269
  if (!sessionFileUUID) {
29833
- const jsonlFiles = fs31.readdirSync(sessionsDir).filter((f) => f.endsWith(".jsonl") && !f.includes(".archived.") && !f.includes(".compaction.")).map((f) => ({ name: f, mtime: fs31.statSync(path33.join(sessionsDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
30270
+ const jsonlFiles = fs32.readdirSync(sessionsDir).filter((f) => f.endsWith(".jsonl") && !f.includes(".archived.") && !f.includes(".compaction.")).map((f) => ({ name: f, mtime: fs32.statSync(path34.join(sessionsDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
29834
30271
  if (jsonlFiles.length === 0) {
29835
30272
  console.error("\u274C \u627E\u4E0D\u5230\u6D3B\u8DC3\u7684 session JSONL \u6587\u4EF6");
29836
30273
  process.exit(1);
29837
30274
  }
29838
30275
  sessionFileUUID = jsonlFiles[0].name.replace(".jsonl", "");
29839
30276
  }
29840
- let jsonlPath = path33.join(sessionsDir, `${sessionFileUUID}.jsonl`);
29841
- if (!fs31.existsSync(jsonlPath)) {
29842
- const jsonlFiles = fs31.readdirSync(sessionsDir).filter((f) => f.endsWith(".jsonl") && !f.includes(".archived.") && !f.includes(".compaction.")).map((f) => ({ name: f, mtime: fs31.statSync(path33.join(sessionsDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
30277
+ let jsonlPath = path34.join(sessionsDir, `${sessionFileUUID}.jsonl`);
30278
+ if (!fs32.existsSync(jsonlPath)) {
30279
+ const jsonlFiles = fs32.readdirSync(sessionsDir).filter((f) => f.endsWith(".jsonl") && !f.includes(".archived.") && !f.includes(".compaction.")).map((f) => ({ name: f, mtime: fs32.statSync(path34.join(sessionsDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
29843
30280
  if (jsonlFiles.length === 0) {
29844
30281
  console.error(`\u274C JSONL \u6587\u4EF6\u4E0D\u5B58\u5728: ${jsonlPath}`);
29845
30282
  process.exit(1);
29846
30283
  }
29847
30284
  sessionFileUUID = jsonlFiles[0].name.replace(".jsonl", "");
29848
- jsonlPath = path33.join(sessionsDir, `${sessionFileUUID}.jsonl`);
30285
+ jsonlPath = path34.join(sessionsDir, `${sessionFileUUID}.jsonl`);
29849
30286
  }
29850
30287
  console.log(` file: ${jsonlPath}`);
29851
- const configName = path33.basename(configPath2);
30288
+ const configName = path34.basename(configPath2);
29852
30289
  if (autoRestart) {
29853
30290
  try {
29854
30291
  const myPid = process.pid;
@@ -29864,15 +30301,15 @@ engine7 session \u2014 \u4F1A\u8BDD\u7BA1\u7406\u5DE5\u5177
29864
30301
  }
29865
30302
  }
29866
30303
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
29867
- const { readFileSync: readFileSync43, writeFileSync: writeFileSync24, renameSync: renameSync5 } = fs31;
30304
+ const { readFileSync: readFileSync44, writeFileSync: writeFileSync25, renameSync: renameSync5 } = fs32;
29868
30305
  if (mode === "archive") {
29869
30306
  const archiveName = `${sessionFileUUID}.jsonl.archived.${timestamp}`;
29870
- renameSync5(jsonlPath, path33.join(sessionsDir, archiveName));
30307
+ renameSync5(jsonlPath, path34.join(sessionsDir, archiveName));
29871
30308
  console.log(`
29872
30309
  \u2705 \u5F52\u6863\u5B8C\u6210: ${archiveName}`);
29873
30310
  console.log(` \u91CD\u542F\u540E\u5C06\u4ECE\u7A7A\u767D\u4F1A\u8BDD\u5F00\u59CB`);
29874
30311
  } else if (mode === "drop-last") {
29875
- const lines = readFileSync43(jsonlPath, "utf-8").trim().split("\n");
30312
+ const lines = readFileSync44(jsonlPath, "utf-8").trim().split("\n");
29876
30313
  const parsed = lines.map((l) => {
29877
30314
  try {
29878
30315
  return JSON.parse(l);
@@ -29891,12 +30328,12 @@ engine7 session \u2014 \u4F1A\u8BDD\u7BA1\u7406\u5DE5\u5177
29891
30328
  }
29892
30329
  const kept = parsed.slice(0, cutIndex);
29893
30330
  const newContent = kept.map((p) => JSON.stringify(p)).join("\n") + "\n";
29894
- writeFileSync24(jsonlPath, newContent);
30331
+ writeFileSync25(jsonlPath, newContent);
29895
30332
  console.log(`
29896
30333
  \u2705 \u780D\u6389\u6700\u8FD1 ${parsed.length - cutIndex} \u884C\uFF08${dropN} \u8F6E\uFF09`);
29897
30334
  console.log(` \u4FDD\u7559 ${kept.length} \u884C`);
29898
30335
  } else if (mode === "strip-images") {
29899
- const lines = readFileSync43(jsonlPath, "utf-8").trim().split("\n");
30336
+ const lines = readFileSync44(jsonlPath, "utf-8").trim().split("\n");
29900
30337
  let stripped = 0;
29901
30338
  const newLines = lines.map((line) => {
29902
30339
  try {
@@ -29937,17 +30374,17 @@ engine7 session \u2014 \u4F1A\u8BDD\u7BA1\u7406\u5DE5\u5177
29937
30374
  return line;
29938
30375
  }
29939
30376
  });
29940
- writeFileSync24(jsonlPath, newLines.join("\n") + "\n");
30377
+ writeFileSync25(jsonlPath, newLines.join("\n") + "\n");
29941
30378
  console.log(`
29942
30379
  \u2705 \u6458\u9664 ${stripped} \u884C\u56FE\u7247\u76F8\u5173\u5185\u5BB9`);
29943
30380
  }
29944
30381
  if (autoRestart) {
29945
- const realCliPath = fs31.realpathSync(process.argv[1]);
29946
- const cliDir = path33.dirname(realCliPath);
29947
- const pkgRoot = path33.resolve(cliDir, "..");
29948
- let enginePath = path33.join(pkgRoot, "dist", "main.mjs");
29949
- if (!fs31.existsSync(enginePath)) {
29950
- enginePath = path33.join(cliDir, "main.mjs");
30382
+ const realCliPath = fs32.realpathSync(process.argv[1]);
30383
+ const cliDir = path34.dirname(realCliPath);
30384
+ const pkgRoot = path34.resolve(cliDir, "..");
30385
+ let enginePath = path34.join(pkgRoot, "dist", "main.mjs");
30386
+ if (!fs32.existsSync(enginePath)) {
30387
+ enginePath = path34.join(cliDir, "main.mjs");
29951
30388
  }
29952
30389
  console.log(`
29953
30390
  \u{1F680} \u62C9\u8D77 engine...`);
@@ -29987,18 +30424,18 @@ engine7 service \u2014 \u5F00\u673A\u81EA\u542F\u52A8\u7BA1\u7406
29987
30424
  let configPath2 = cfg;
29988
30425
  let cwd = svcCwd;
29989
30426
  if (!configPath2) {
29990
- const defaultCfg = path33.join(cwd, "configs", "main7.json");
29991
- const homeCfg = path33.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7", "configs", "main7.json");
29992
- if (fs31.existsSync(defaultCfg)) {
30427
+ const defaultCfg = path34.join(cwd, "configs", "main7.json");
30428
+ const homeCfg = path34.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7", "configs", "main7.json");
30429
+ if (fs32.existsSync(defaultCfg)) {
29993
30430
  configPath2 = defaultCfg;
29994
- } else if (fs31.existsSync(homeCfg)) {
30431
+ } else if (fs32.existsSync(homeCfg)) {
29995
30432
  configPath2 = homeCfg;
29996
- cwd = path33.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
30433
+ cwd = path34.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7");
29997
30434
  } else {
29998
30435
  const ptr = readHomePointer();
29999
30436
  if (ptr?.stateDir) {
30000
- const ptrCfg = path33.join(ptr.stateDir, "configs", "main7.json");
30001
- if (fs31.existsSync(ptrCfg)) {
30437
+ const ptrCfg = path34.join(ptr.stateDir, "configs", "main7.json");
30438
+ if (fs32.existsSync(ptrCfg)) {
30002
30439
  configPath2 = ptrCfg;
30003
30440
  cwd = ptr.stateDir;
30004
30441
  }
@@ -30016,27 +30453,27 @@ engine7 service \u2014 \u5F00\u673A\u81EA\u542F\u52A8\u7BA1\u7406
30016
30453
  if (action === "install") {
30017
30454
  if (process.platform === "win32") {
30018
30455
  const taskName = "Engine7";
30019
- const wrapperPath = path33.join(cwd, "engine7-start.cmd");
30456
+ const wrapperPath = path34.join(cwd, "engine7-start.cmd");
30020
30457
  const wrapperContent = buildServiceWrapper({
30021
30458
  cwd,
30022
30459
  nodeExe: nodeExe || void 0,
30023
- cliMjs: path33.resolve(engine7Bin),
30024
- configPath: path33.resolve(configPath2)
30460
+ cliMjs: path34.resolve(engine7Bin),
30461
+ configPath: path34.resolve(configPath2)
30025
30462
  });
30026
- fs31.writeFileSync(wrapperPath, wrapperContent);
30027
- const startupDir = path33.join(
30028
- process.env.APPDATA || path33.join(process.env.USERPROFILE || "", "AppData", "Roaming"),
30463
+ fs32.writeFileSync(wrapperPath, wrapperContent);
30464
+ const startupDir = path34.join(
30465
+ process.env.APPDATA || path34.join(process.env.USERPROFILE || "", "AppData", "Roaming"),
30029
30466
  "Microsoft",
30030
30467
  "Windows",
30031
30468
  "Start Menu",
30032
30469
  "Programs",
30033
30470
  "Startup"
30034
30471
  );
30035
- fs31.mkdirSync(startupDir, { recursive: true });
30036
- const vbsPath = path33.join(startupDir, "Engine7.vbs");
30472
+ fs32.mkdirSync(startupDir, { recursive: true });
30473
+ const vbsPath = path34.join(startupDir, "Engine7.vbs");
30037
30474
  const nodeExePath = nodeExe || process.execPath;
30038
- const cliMjsPath = path33.resolve(engine7Bin);
30039
- const absCfg = path33.resolve(configPath2);
30475
+ const cliMjsPath = path34.resolve(engine7Bin);
30476
+ const absCfg = path34.resolve(configPath2);
30040
30477
  const vbs = [
30041
30478
  "' Engine 7 \u5F00\u673A\u81EA\u542F\uFF08\u542F\u52A8\u6587\u4EF6\u5939\uFF0C\u514D\u7BA1\u7406\u5458\uFF09",
30042
30479
  'Set fso = CreateObject("Scripting.FileSystemObject")',
@@ -30052,15 +30489,15 @@ engine7 service \u2014 \u5F00\u673A\u81EA\u542F\u52A8\u7BA1\u7406
30052
30489
  's.Run """' + nodeExePath + '"" ""' + cliMjsPath + '"" start --config ""' + absCfg + '""", 0, False',
30053
30490
  ""
30054
30491
  ].join(String.fromCharCode(13) + String.fromCharCode(10));
30055
- fs31.writeFileSync(vbsPath, vbs);
30492
+ fs32.writeFileSync(vbsPath, vbs);
30056
30493
  console.log(`\u2705 \u5F00\u673A\u81EA\u542F\u52A8\u5DF2\u6CE8\u518C\uFF08\u542F\u52A8\u6587\u4EF6\u5939: ${vbsPath}\uFF09`);
30057
30494
  } else if (process.platform === "darwin") {
30058
30495
  const label = "com.engine7.agent";
30059
- const plistDir = path33.join(process.env.HOME, "Library", "LaunchAgents");
30060
- fs31.mkdirSync(plistDir, { recursive: true });
30061
- const plistPath = path33.join(plistDir, `${label}.plist`);
30062
- const localCli = path33.join("node_modules", "engine7", "dist", "cli.mjs");
30063
- const cliMjs = fs31.existsSync(localCli) ? path33.resolve(localCli) : path33.join(path33.dirname(path33.dirname(engine7Bin)), "lib", "node_modules", "engine7", "dist", "cli.mjs");
30496
+ const plistDir = path34.join(process.env.HOME, "Library", "LaunchAgents");
30497
+ fs32.mkdirSync(plistDir, { recursive: true });
30498
+ const plistPath = path34.join(plistDir, `${label}.plist`);
30499
+ const localCli = path34.join("node_modules", "engine7", "dist", "cli.mjs");
30500
+ const cliMjs = fs32.existsSync(localCli) ? path34.resolve(localCli) : path34.join(path34.dirname(path34.dirname(engine7Bin)), "lib", "node_modules", "engine7", "dist", "cli.mjs");
30064
30501
  const plist = `<?xml version="1.0" encoding="UTF-8"?>
30065
30502
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
30066
30503
  <plist version="1.0">
@@ -30072,14 +30509,14 @@ engine7 service \u2014 \u5F00\u673A\u81EA\u542F\u52A8\u7BA1\u7406
30072
30509
  <string>${cliMjs}</string>
30073
30510
  <string>start</string>
30074
30511
  <string>--config</string>
30075
- <string>${path33.resolve(configPath2)}</string>
30512
+ <string>${path34.resolve(configPath2)}</string>
30076
30513
  </array>
30077
30514
  <key>WorkingDirectory</key><string>${cwd}</string>
30078
30515
  <key>RunAtLoad</key><true/>
30079
30516
  <key>KeepAlive</key><true/>
30080
30517
  </dict>
30081
30518
  </plist>`;
30082
- fs31.writeFileSync(plistPath, plist);
30519
+ fs32.writeFileSync(plistPath, plist);
30083
30520
  try {
30084
30521
  execSync3(`launchctl load "${plistPath}"`, { stdio: "inherit" });
30085
30522
  console.log(`\u2705 Mac launchd \u670D\u52A1\u5DF2\u521B\u5EFA\uFF08\u5F00\u673A\u81EA\u542F\u52A8\uFF09`);
@@ -30090,9 +30527,9 @@ engine7 service \u2014 \u5F00\u673A\u81EA\u542F\u52A8\u7BA1\u7406
30090
30527
  }
30091
30528
  } else {
30092
30529
  const svcName = "engine7";
30093
- const svcDir = path33.join(process.env.HOME, ".config", "systemd", "user");
30094
- fs31.mkdirSync(svcDir, { recursive: true });
30095
- const svcPath = path33.join(svcDir, `${svcName}.service`);
30530
+ const svcDir = path34.join(process.env.HOME, ".config", "systemd", "user");
30531
+ fs32.mkdirSync(svcDir, { recursive: true });
30532
+ const svcPath = path34.join(svcDir, `${svcName}.service`);
30096
30533
  const svc = `[Unit]
30097
30534
  Description=Engine 7 Agent
30098
30535
  After=network.target
@@ -30100,13 +30537,13 @@ After=network.target
30100
30537
  [Service]
30101
30538
  Type=simple
30102
30539
  WorkingDirectory=${cwd}
30103
- ExecStart=${process.execPath} ${engine7Bin} start --config ${path33.resolve(configPath2)}
30540
+ ExecStart=${process.execPath} ${engine7Bin} start --config ${path34.resolve(configPath2)}
30104
30541
  Restart=on-failure
30105
30542
  RestartSec=10
30106
30543
 
30107
30544
  [Install]
30108
30545
  WantedBy=default.target`;
30109
- fs31.writeFileSync(svcPath, svc);
30546
+ fs32.writeFileSync(svcPath, svc);
30110
30547
  try {
30111
30548
  execSync3(`systemctl --user daemon-reload`, { stdio: "inherit" });
30112
30549
  execSync3(`systemctl --user enable ${svcName}`, { stdio: "inherit" });
@@ -30119,8 +30556,8 @@ WantedBy=default.target`;
30119
30556
  }
30120
30557
  } else if (action === "uninstall") {
30121
30558
  if (process.platform === "win32") {
30122
- const vbsPath = path33.join(
30123
- process.env.APPDATA || path33.join(process.env.USERPROFILE || "", "AppData", "Roaming"),
30559
+ const vbsPath = path34.join(
30560
+ process.env.APPDATA || path34.join(process.env.USERPROFILE || "", "AppData", "Roaming"),
30124
30561
  "Microsoft",
30125
30562
  "Windows",
30126
30563
  "Start Menu",
@@ -30132,20 +30569,20 @@ WantedBy=default.target`;
30132
30569
  execSync3(`schtasks /delete /tn "Engine7" /f`, { stdio: "ignore" });
30133
30570
  } catch {
30134
30571
  }
30135
- if (fs31.existsSync(vbsPath)) {
30136
- fs31.rmSync(vbsPath, { force: true });
30572
+ if (fs32.existsSync(vbsPath)) {
30573
+ fs32.rmSync(vbsPath, { force: true });
30137
30574
  console.log(`\u2705 \u5F00\u673A\u81EA\u542F\u52A8\u5DF2\u79FB\u9664\uFF08${vbsPath}\uFF09`);
30138
30575
  } else {
30139
30576
  console.log("\uFF08\u5F00\u673A\u81EA\u542F\u52A8\u672C\u6765\u5C31\u6CA1\u6CE8\u518C\uFF09");
30140
30577
  }
30141
30578
  } else if (process.platform === "darwin") {
30142
- const plistPath = path33.join(process.env.HOME, "Library", "LaunchAgents", "com.engine7.agent.plist");
30579
+ const plistPath = path34.join(process.env.HOME, "Library", "LaunchAgents", "com.engine7.agent.plist");
30143
30580
  try {
30144
30581
  execSync3(`launchctl unload "${plistPath}"`, { stdio: "inherit" });
30145
30582
  } catch {
30146
30583
  }
30147
30584
  try {
30148
- fs31.unlinkSync(plistPath);
30585
+ fs32.unlinkSync(plistPath);
30149
30586
  } catch {
30150
30587
  }
30151
30588
  console.log(`\u2705 Mac launchd \u670D\u52A1\u5DF2\u5220\u9664`);
@@ -30154,9 +30591,9 @@ WantedBy=default.target`;
30154
30591
  execSync3(`systemctl --user disable engine7`, { stdio: "inherit" });
30155
30592
  } catch {
30156
30593
  }
30157
- const svcPath = path33.join(process.env.HOME, ".config", "systemd", "user", "engine7.service");
30594
+ const svcPath = path34.join(process.env.HOME, ".config", "systemd", "user", "engine7.service");
30158
30595
  try {
30159
- fs31.unlinkSync(svcPath);
30596
+ fs32.unlinkSync(svcPath);
30160
30597
  } catch {
30161
30598
  }
30162
30599
  try {
@@ -30167,8 +30604,8 @@ WantedBy=default.target`;
30167
30604
  }
30168
30605
  } else if (action === "status") {
30169
30606
  if (process.platform === "win32") {
30170
- const vbsPath = path33.join(
30171
- process.env.APPDATA || path33.join(process.env.USERPROFILE || "", "AppData", "Roaming"),
30607
+ const vbsPath = path34.join(
30608
+ process.env.APPDATA || path34.join(process.env.USERPROFILE || "", "AppData", "Roaming"),
30172
30609
  "Microsoft",
30173
30610
  "Windows",
30174
30611
  "Start Menu",
@@ -30176,10 +30613,10 @@ WantedBy=default.target`;
30176
30613
  "Startup",
30177
30614
  "Engine7.vbs"
30178
30615
  );
30179
- console.log(fs31.existsSync(vbsPath) ? `\u2705 \u5DF2\u6CE8\u518C\uFF08\u542F\u52A8\u6587\u4EF6\u5939\uFF09: ${vbsPath}` : "\u274C \u672A\u6CE8\u518C");
30616
+ console.log(fs32.existsSync(vbsPath) ? `\u2705 \u5DF2\u6CE8\u518C\uFF08\u542F\u52A8\u6587\u4EF6\u5939\uFF09: ${vbsPath}` : "\u274C \u672A\u6CE8\u518C");
30180
30617
  } else if (process.platform === "darwin") {
30181
- const plistPath = path33.join(process.env.HOME, "Library", "LaunchAgents", "com.engine7.agent.plist");
30182
- if (fs31.existsSync(plistPath)) {
30618
+ const plistPath = path34.join(process.env.HOME, "Library", "LaunchAgents", "com.engine7.agent.plist");
30619
+ if (fs32.existsSync(plistPath)) {
30183
30620
  console.log("\u2705 \u5DF2\u5B89\u88C5\uFF08launchd\uFF09");
30184
30621
  } else {
30185
30622
  console.log("\u274C \u672A\u5B89\u88C5");
@@ -30212,8 +30649,8 @@ WantedBy=default.target`;
30212
30649
  console.log(`
30213
30650
  \u{1F680} engine7 init`);
30214
30651
  console.log(` state-dir: ${opts.stateDir}`);
30215
- if (fs31.existsSync(opts.stateDir) && !opts.dryRun) {
30216
- const files = fs31.readdirSync(opts.stateDir);
30652
+ if (fs32.existsSync(opts.stateDir) && !opts.dryRun) {
30653
+ const files = fs32.readdirSync(opts.stateDir);
30217
30654
  if (files.length > 0) {
30218
30655
  if (opts.force) {
30219
30656
  if (looksLikeAppInstall(opts.stateDir)) {
@@ -30226,10 +30663,10 @@ WantedBy=default.target`;
30226
30663
  }
30227
30664
  console.log(`
30228
30665
  \u26A0\uFE0F --force: \u6E05\u7A7A\u5DF2\u6709\u76EE\u5F55 ${opts.stateDir}`);
30229
- fs31.rmSync(opts.stateDir, { recursive: true, force: true });
30666
+ fs32.rmSync(opts.stateDir, { recursive: true, force: true });
30230
30667
  } else {
30231
- const existingConfig = path33.join(opts.stateDir, "configs", "main7.json");
30232
- if (fs31.existsSync(existingConfig)) {
30668
+ const existingConfig = path34.join(opts.stateDir, "configs", "main7.json");
30669
+ if (fs32.existsSync(existingConfig)) {
30233
30670
  console.log(`
30234
30671
  \u267B\uFE0F \u68C0\u6D4B\u5230\u5DF2\u6709\u914D\u7F6E: ${existingConfig}`);
30235
30672
  console.log(` \u2192 \u590D\u8DD1\u6A21\u5F0F\uFF1A\u663E\u793A\u73B0\u6709\u914D\u7F6E\uFF0C\u56DE\u8F66\u4FDD\u7559\uFF0C\u6539\u4E86\u624D\u751F\u6548\uFF08\u4E0D\u4F1A\u6E05\u7A7A\u76EE\u5F55\uFF09`);
@@ -30310,12 +30747,12 @@ WantedBy=default.target`;
30310
30747
  let photoSrc = "";
30311
30748
  if (answers?.selfie?.photoPath) {
30312
30749
  const { dimsOf: dimsOf2 } = await Promise.resolve().then(() => (init_my_selfie(), my_selfie_exports));
30313
- const src = path33.resolve(answers.selfie.photoPath.trim());
30314
- const ext = path33.extname(src).toLowerCase();
30750
+ const src = path34.resolve(answers.selfie.photoPath.trim());
30751
+ const ext = path34.extname(src).toLowerCase();
30315
30752
  if (!PHOTO_EXTS.includes(ext)) {
30316
30753
  throw new Error(`\u7167\u7247\u683C\u5F0F\u4E0D\u652F\u6301\uFF1A${ext || "(\u6CA1\u6709\u540E\u7F00)"}\u3002\u8BF7\u7528 PNG / JPEG / WebP\u3002`);
30317
30754
  }
30318
- if (!fs31.existsSync(src)) {
30755
+ if (!fs32.existsSync(src)) {
30319
30756
  throw new Error(`\u7167\u7247\u4E0D\u5B58\u5728\uFF1A${src}`);
30320
30757
  }
30321
30758
  const dims = dimsOf2(src);
@@ -30326,7 +30763,7 @@ WantedBy=default.target`;
30326
30763
  photoSrc = src;
30327
30764
  }
30328
30765
  const config = generateConfig(values);
30329
- const configPath = path33.join(opts.stateDir, "configs", "main7.json");
30766
+ const configPath = path34.join(opts.stateDir, "configs", "main7.json");
30330
30767
  const tree = buildDirTree(values);
30331
30768
  if (headlessEmit) headlessEmit({ event: "progress", step: "write_config" });
30332
30769
  tree.push({
@@ -30338,22 +30775,22 @@ WantedBy=default.target`;
30338
30775
  dryRun(tree);
30339
30776
  const skillsSrc = findTemplateDir("skills");
30340
30777
  if (skillsSrc) {
30341
- const skills = fs31.readdirSync(skillsSrc);
30778
+ const skills = fs32.readdirSync(skillsSrc);
30342
30779
  for (const s of skills) {
30343
- console.log(` \u{1F4C1} ${path33.join(opts.stateDir, "workspace", "skills", s)}`);
30780
+ console.log(` \u{1F4C1} ${path34.join(opts.stateDir, "workspace", "skills", s)}`);
30344
30781
  }
30345
30782
  }
30346
30783
  } else {
30347
30784
  if (values.chatMode === "auto") {
30348
30785
  try {
30349
- fs31.unlinkSync(path33.join(opts.stateDir, "workspace", ".work-mode"));
30786
+ fs32.unlinkSync(path34.join(opts.stateDir, "workspace", ".work-mode"));
30350
30787
  } catch {
30351
30788
  }
30352
30789
  }
30353
30790
  if (photoSrc && values.selfiePhotoRelPath) {
30354
- const dest = path33.join(opts.stateDir, "workspace", values.selfiePhotoRelPath);
30355
- fs31.mkdirSync(path33.dirname(dest), { recursive: true });
30356
- fs31.copyFileSync(photoSrc, dest);
30791
+ const dest = path34.join(opts.stateDir, "workspace", values.selfiePhotoRelPath);
30792
+ fs32.mkdirSync(path34.dirname(dest), { recursive: true });
30793
+ fs32.copyFileSync(photoSrc, dest);
30357
30794
  console.log(` \u{1F5BC} \u5F62\u8C61: ${dest}`);
30358
30795
  }
30359
30796
  if (headlessEmit) headlessEmit({ event: "progress", step: "create_dirs" });
@@ -30369,58 +30806,58 @@ WantedBy=default.target`;
30369
30806
  if (headlessEmit) headlessEmit({ event: "progress", step: "copy_skills" });
30370
30807
  const skillsSrc = findTemplateDir("skills");
30371
30808
  if (skillsSrc) {
30372
- const skillsDest = path33.join(opts.stateDir, "workspace", "skills");
30373
- fs31.mkdirSync(skillsDest, { recursive: true });
30374
- const skills = fs31.readdirSync(skillsSrc);
30809
+ const skillsDest = path34.join(opts.stateDir, "workspace", "skills");
30810
+ fs32.mkdirSync(skillsDest, { recursive: true });
30811
+ const skills = fs32.readdirSync(skillsSrc);
30375
30812
  for (const s of skills) {
30376
- copyDirRecursive(path33.join(skillsSrc, s), path33.join(skillsDest, s));
30813
+ copyDirRecursive(path34.join(skillsSrc, s), path34.join(skillsDest, s));
30377
30814
  console.log(` \u{1F4E6} skill: ${s}`);
30378
30815
  }
30379
30816
  }
30380
30817
  console.log(`
30381
30818
  \u2705 \u521D\u59CB\u5316\u5B8C\u6210\uFF01
30382
30819
  `);
30383
- const homePointer = path33.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7.json");
30820
+ const homePointer = path34.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7.json");
30384
30821
  const pointerData = { stateDir: opts.stateDir };
30385
- fs31.writeFileSync(homePointer, JSON.stringify(pointerData, null, 2) + "\n");
30822
+ fs32.writeFileSync(homePointer, JSON.stringify(pointerData, null, 2) + "\n");
30386
30823
  console.log(` \u{1F4C4} ${homePointer}\uFF08\u4ECE\u4EFB\u610F\u76EE\u5F55\u90FD\u80FD\u627E\u5230\u6B64 agent\uFF09`);
30387
30824
  console.log(`\u4E0B\u4E00\u6B65\uFF1A`);
30388
30825
  console.log(` 1. \u68C0\u67E5\u5E76\u4FEE\u6539 ${opts.stateDir}/configs/main7.json`);
30389
30826
  console.log(` 2. \u542F\u52A8 Engine: engine7 start\uFF08\u5728\u4EFB\u610F\u76EE\u5F55\u90FD\u884C\uFF09`);
30390
30827
  console.log(` 3. \u5F00\u673A\u81EA\u542F: engine7 service install\uFF08\u5728\u4EFB\u610F\u76EE\u5F55\u90FD\u884C\uFF09`);
30391
- console.log(` 4. \u67E5\u770B workspace: ${path33.join(opts.stateDir, "workspace")}`);
30828
+ console.log(` 4. \u67E5\u770B workspace: ${path34.join(opts.stateDir, "workspace")}`);
30392
30829
  if (headlessEmit) {
30393
30830
  headlessEmit({ event: "done", stateDir: opts.stateDir.replace(/\\/g, "/"), configPath: configPath.replace(/\\/g, "/") });
30394
30831
  }
30395
30832
  }
30396
30833
  }
30397
30834
  function copyDirRecursive(src, dest) {
30398
- fs31.mkdirSync(dest, { recursive: true });
30399
- for (const entry of fs31.readdirSync(src, { withFileTypes: true })) {
30400
- const srcPath = path33.join(src, entry.name);
30401
- const destPath = path33.join(dest, entry.name);
30835
+ fs32.mkdirSync(dest, { recursive: true });
30836
+ for (const entry of fs32.readdirSync(src, { withFileTypes: true })) {
30837
+ const srcPath = path34.join(src, entry.name);
30838
+ const destPath = path34.join(dest, entry.name);
30402
30839
  if (entry.isDirectory()) {
30403
30840
  copyDirRecursive(srcPath, destPath);
30404
30841
  } else {
30405
- fs31.copyFileSync(srcPath, destPath);
30842
+ fs32.copyFileSync(srcPath, destPath);
30406
30843
  }
30407
30844
  }
30408
30845
  }
30409
30846
  function findTemplateDir(name) {
30410
30847
  const candidates = [
30411
- path33.join(__dirname, "..", "templates", name),
30412
- path33.join(__dirname, "..", "..", "templates", name),
30413
- path33.join(process.cwd(), "templates", name)
30848
+ path34.join(__dirname, "..", "templates", name),
30849
+ path34.join(__dirname, "..", "..", "templates", name),
30850
+ path34.join(process.cwd(), "templates", name)
30414
30851
  ];
30415
30852
  for (const p of candidates) {
30416
- if (fs31.existsSync(p)) return p;
30853
+ if (fs32.existsSync(p)) return p;
30417
30854
  }
30418
30855
  return null;
30419
30856
  }
30420
30857
  function readHomePointer() {
30421
- const ptrPath = path33.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7.json");
30858
+ const ptrPath = path34.join(process.env.HOME || process.env.USERPROFILE || ".", ".engine7.json");
30422
30859
  try {
30423
- return JSON.parse(fs31.readFileSync(ptrPath, "utf-8"));
30860
+ return JSON.parse(fs32.readFileSync(ptrPath, "utf-8"));
30424
30861
  } catch {
30425
30862
  return null;
30426
30863
  }