ai-helper-agent 2.2.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -1
- package/README.zh-CN.md +94 -1
- package/dist/assets/{agent-store-DOyMdB_7.js → agent-store-DcAs6YyA.js} +2 -2
- package/dist/assets/{agent-store-DOyMdB_7.js.map → agent-store-DcAs6YyA.js.map} +1 -1
- package/dist/assets/background.js +83 -89
- package/dist/assets/background.js.map +1 -1
- package/dist/assets/content.js +81 -65
- package/dist/assets/{i18n-MmYITlVj.js → i18n-C4jSRoCH.js} +5 -5
- package/dist/assets/i18n-C4jSRoCH.js.map +1 -0
- package/dist/assets/{model-profiles-CV_sUuPf.js → model-profiles-Dl_MsXWa.js} +5 -5
- package/dist/assets/model-profiles-Dl_MsXWa.js.map +1 -0
- package/dist/assets/options-8-nu3P5K.css +1 -0
- package/dist/assets/options.js +264 -197
- package/dist/assets/options.js.map +1 -1
- package/dist/assets/{page-snapshot-renderer-CE53ibi-.js → page-snapshot-renderer-CVL6r6Kf.js} +2 -2
- package/dist/assets/{page-snapshot-renderer-CE53ibi-.js.map → page-snapshot-renderer-CVL6r6Kf.js.map} +1 -1
- package/dist/assets/{session-store-B0ZPGm7d.js → session-store-CzVQCgQ_.js} +4 -4
- package/dist/assets/session-store-CzVQCgQ_.js.map +1 -0
- package/dist/assets/side_panel.css +1 -1
- package/dist/assets/side_panel.js +327 -293
- package/dist/assets/side_panel.js.map +1 -1
- package/dist/assets/version-CNG2KGnJ.js +2 -0
- package/dist/assets/version-CNG2KGnJ.js.map +1 -0
- package/dist/libs/jspdf.min.js +1 -1
- package/dist/manifest.json +2 -2
- package/dist/offscreen/offscreen.js +4 -4
- package/dist/options.html +140 -3
- package/dist/side_panel.html +27 -4
- package/package.json +1 -1
- package/src/config.js +72 -24
- package/src/crypto-lite.js +31 -0
- package/src/locales/en.json +55 -0
- package/src/locales/zh.json +55 -0
- package/src/memory/embedding-config.js +95 -0
- package/src/memory/fusion.js +60 -0
- package/src/memory/injection.js +118 -0
- package/src/memory/routes.js +193 -0
- package/src/memory/service.js +957 -0
- package/src/memory/tags.js +62 -0
- package/src/memory/tier.js +37 -0
- package/src/memory/tokens.js +158 -0
- package/src/memory/vectors.js +128 -0
- package/src/rag/embedding/index.js +7 -0
- package/src/rag/embedding/local.js +25 -0
- package/src/rag/keyword-match.js +112 -0
- package/src/rag/manager.js +15 -0
- package/src/rag/searcher.js +15 -87
- package/src/server.js +6 -0
- package/dist/assets/i18n-MmYITlVj.js.map +0 -1
- package/dist/assets/model-profiles-CV_sUuPf.js.map +0 -1
- package/dist/assets/options-Df_1Dtn8.css +0 -1
- package/dist/assets/session-store-B0ZPGm7d.js.map +0 -1
- /package/dist/_locales/{zh → zh_CN}/messages.json +0 -0
package/dist/options.html
CHANGED
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title data-i18n="settings.docTitle">AI Helper - 配置</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/i18n-
|
|
8
|
-
<script type="module" crossorigin src="/assets/model-profiles-
|
|
7
|
+
<script type="module" crossorigin src="/assets/i18n-C4jSRoCH.js"></script>
|
|
8
|
+
<script type="module" crossorigin src="/assets/model-profiles-Dl_MsXWa.js"></script>
|
|
9
|
+
<script type="module" crossorigin src="/assets/version-CNG2KGnJ.js"></script>
|
|
9
10
|
<script type="module" crossorigin src="/assets/options.js"></script>
|
|
10
11
|
<script type="module" crossorigin src="/assets/modulepreload-polyfill-P2Xu9kJm.js"></script>
|
|
11
|
-
<link rel="stylesheet" crossorigin href="/assets/options-
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/options-8-nu3P5K.css">
|
|
12
13
|
</head>
|
|
13
14
|
<body>
|
|
14
15
|
<div class="container">
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
<button class="tab-nav-btn" data-tab="agent" data-i18n="settings.tabAgent">代理</button>
|
|
44
45
|
<button class="tab-nav-btn" data-tab="toolbox" data-i18n="settings.tabToolbox">扩展</button>
|
|
45
46
|
<button class="tab-nav-btn" data-tab="knowledge" data-i18n="knowledge.tabTitle">知识库</button>
|
|
47
|
+
<button class="tab-nav-btn" data-tab="memory" data-i18n="memoryPanel.tabTitle">记忆</button>
|
|
46
48
|
</div>
|
|
47
49
|
|
|
48
50
|
<div class="tab-panel active" data-tab="basic">
|
|
@@ -580,6 +582,29 @@
|
|
|
580
582
|
</div>
|
|
581
583
|
</div>
|
|
582
584
|
|
|
585
|
+
<!-- 代理端安装指引(与当前插件版本匹配,一键复制) -->
|
|
586
|
+
<div class="agent-install-guide" id="agentInstallGuide">
|
|
587
|
+
<div class="agent-install-guide-header">
|
|
588
|
+
<h3 id="agentInstallTitle" data-i18n="agentConfig.installGuideTitle">安装代理端</h3>
|
|
589
|
+
<button class="btn btn-primary" id="copyInstallAuto" data-i18n="agentConfig.copyCmdAuto">一键复制(按当前系统)</button>
|
|
590
|
+
</div>
|
|
591
|
+
<p class="agent-install-guide-hint" data-i18n="agentConfig.installGuideHint">复制下方命令到终端执行,将安装与当前插件版本一致的代理端,避免版本不匹配导致无法连接。</p>
|
|
592
|
+
<div class="agent-install-cmd-row">
|
|
593
|
+
<div class="agent-install-cmd-main">
|
|
594
|
+
<span class="agent-install-os-label" data-i18n="agentConfig.installGuideMacLinux">macOS / Linux</span>
|
|
595
|
+
<code class="agent-install-cmd" id="installCmdUnix"></code>
|
|
596
|
+
</div>
|
|
597
|
+
<button class="btn btn-secondary agent-install-copy-btn" id="copyInstallUnix" data-i18n="agentConfig.copyCmd">复制</button>
|
|
598
|
+
</div>
|
|
599
|
+
<div class="agent-install-cmd-row">
|
|
600
|
+
<div class="agent-install-cmd-main">
|
|
601
|
+
<span class="agent-install-os-label">Windows (PowerShell)</span>
|
|
602
|
+
<code class="agent-install-cmd" id="installCmdWin"></code>
|
|
603
|
+
</div>
|
|
604
|
+
<button class="btn btn-secondary agent-install-copy-btn" id="copyInstallWin" data-i18n="agentConfig.copyCmd">复制</button>
|
|
605
|
+
</div>
|
|
606
|
+
</div>
|
|
607
|
+
|
|
583
608
|
<div class="info-box collapsible collapsed" style="margin-top: 15px;">
|
|
584
609
|
<h3 class="collapsible-header"><span data-i18n="agentConfig.useInstructionTitle">💡 使用说明及安全提示</span> <span class="collapsible-arrow">▶</span></h3>
|
|
585
610
|
<div class="collapsible-body" style="display:none;">
|
|
@@ -737,6 +762,118 @@
|
|
|
737
762
|
</div>
|
|
738
763
|
</div><!-- /tab-knowledge -->
|
|
739
764
|
|
|
765
|
+
<!-- 记忆:语义召回向量配置(数据在 agent 端 config.json 的 memoryEmbedding) -->
|
|
766
|
+
<div class="tab-panel" data-tab="memory">
|
|
767
|
+
<div class="knowledge-header">
|
|
768
|
+
<div class="knowledge-title-row">
|
|
769
|
+
<h2 class="knowledge-title" data-i18n="memoryPanel.title">记忆向量</h2>
|
|
770
|
+
</div>
|
|
771
|
+
<div style="display:flex; align-items:center; gap:16px; flex-shrink:0;">
|
|
772
|
+
<label class="global-toggle">
|
|
773
|
+
<input type="checkbox" id="memoryEnabled">
|
|
774
|
+
<span class="global-toggle-slider"></span>
|
|
775
|
+
<span class="global-toggle-label" id="memoryEnabledLabel"></span>
|
|
776
|
+
</label>
|
|
777
|
+
</div>
|
|
778
|
+
</div>
|
|
779
|
+
|
|
780
|
+
<div id="memoryGate" style="display:none;"></div>
|
|
781
|
+
|
|
782
|
+
<!-- 本 Tab 的配置在点底部「保存」时写入代理 config.json(代理未连接则跳过);底部保存同时存扩展自身设置 -->
|
|
783
|
+
<div class="memory-scope-hint" data-i18n="memoryPanel.scopeHint"></div>
|
|
784
|
+
|
|
785
|
+
<div id="memoryMain">
|
|
786
|
+
<!-- 状态卡:条数 + 指纹陈旧(stale 是本次设计的核心收益) -->
|
|
787
|
+
<div class="form-group">
|
|
788
|
+
<label data-i18n="memoryPanel.statusTitle">向量索引状态</label>
|
|
789
|
+
<div id="memoryStatus" class="memory-status"></div>
|
|
790
|
+
</div>
|
|
791
|
+
|
|
792
|
+
<div class="form-group">
|
|
793
|
+
<label data-i18n="memoryPanel.modeLabel">嵌入来源</label>
|
|
794
|
+
<label style="display:inline-flex; align-items:center; gap:6px; margin-right:16px;">
|
|
795
|
+
<input type="radio" name="memoryEmbeddingMode" value="openai-compat" style="width:auto;">
|
|
796
|
+
<span data-i18n="memoryPanel.modeRemote">远端服务(OpenAI 兼容接口)</span>
|
|
797
|
+
</label>
|
|
798
|
+
<label style="display:inline-flex; align-items:center; gap:6px;">
|
|
799
|
+
<input type="radio" name="memoryEmbeddingMode" value="local" style="width:auto;">
|
|
800
|
+
<span data-i18n="memoryPanel.modeLocal">本地模型</span>
|
|
801
|
+
</label>
|
|
802
|
+
</div>
|
|
803
|
+
|
|
804
|
+
<div class="form-group" id="memoryRemoteGroup">
|
|
805
|
+
<label data-i18n="memoryPanel.baseUrlLabel">服务地址</label>
|
|
806
|
+
<input type="text" id="memoryBaseUrlInput" data-i18n-placeholder="memoryPanel.baseUrlPlaceholder" placeholder="https://api.openai.com/v1">
|
|
807
|
+
</div>
|
|
808
|
+
|
|
809
|
+
<div class="form-group" id="memoryModelGroup">
|
|
810
|
+
<label data-i18n="memoryPanel.modelLabel">模型名称</label>
|
|
811
|
+
<input type="text" id="memoryModelInput" data-i18n-placeholder="memoryPanel.modelPlaceholder" placeholder="BAAI/bge-m3">
|
|
812
|
+
</div>
|
|
813
|
+
|
|
814
|
+
<div class="form-group" id="memoryApiKeyGroup">
|
|
815
|
+
<label data-i18n="memoryPanel.apiKeyLabel">API Key</label>
|
|
816
|
+
<input type="password" id="memoryApiKeyInput" data-i18n-placeholder="memoryPanel.apiKeyPlaceholder" placeholder="">
|
|
817
|
+
<!-- L1:明文 key 永不回填 + 留空=不修改 ⇒ 用户没有清除路径,这是唯一显式清除入口(hasApiKey 时显示) -->
|
|
818
|
+
<button class="kb-action-btn" type="button" id="memoryApiKeyClearBtn" data-i18n="memoryPanel.apiKeyClearBtn" style="display:none; margin-top:8px;">清除已存 Key</button>
|
|
819
|
+
</div>
|
|
820
|
+
|
|
821
|
+
<div class="form-group" id="memoryDimsGroup">
|
|
822
|
+
<label data-i18n="memoryPanel.dimsLabel">向量维度(可留空,测试时自动探测)</label>
|
|
823
|
+
<div class="kb-vec-test-row" id="memoryTestRow">
|
|
824
|
+
<input type="number" id="memoryDimsInput" min="1" max="8192" placeholder="">
|
|
825
|
+
<button class="kb-action-btn" type="button" id="memoryTestBtn" data-i18n="memoryPanel.testBtn">测试连通性</button>
|
|
826
|
+
<span class="kb-vec-test-result" id="memoryTestResult"></span>
|
|
827
|
+
</div>
|
|
828
|
+
</div>
|
|
829
|
+
|
|
830
|
+
<div class="form-group" id="memoryPrefixGroup">
|
|
831
|
+
<label data-i18n="memoryPanel.prefixLabel">查询前缀(queryPrefix,可留空)</label>
|
|
832
|
+
<input type="text" id="memoryPrefixInput" placeholder="">
|
|
833
|
+
</div>
|
|
834
|
+
|
|
835
|
+
<div class="form-group" id="memoryLocalNotice" style="display:none;">
|
|
836
|
+
<span data-i18n="memoryPanel.localNotice">本地档无需测试连通性:保存后直接重建向量索引(首次使用需下载模型,可能数十分钟)。嵌入在 512 token 处静默截断,超长条目只有前部进入向量。</span>
|
|
837
|
+
</div>
|
|
838
|
+
|
|
839
|
+
<!-- 「保存到代理」按钮已并入底部全局「保存配置」:点保存时若代理已连接会自动推到这里 -->
|
|
840
|
+
<div class="form-group" style="display:flex; gap:12px; align-items:center;">
|
|
841
|
+
<button class="toolbox-add-btn" type="button" id="memoryReindexBtn" style="width:auto; margin:0; padding:8px 18px; border-style:solid;" data-i18n="memoryPanel.reindexBtn">重建向量索引</button>
|
|
842
|
+
</div>
|
|
843
|
+
|
|
844
|
+
<div id="memoryResult" class="memory-status" style="display:none;"></div>
|
|
845
|
+
|
|
846
|
+
<!-- 记忆条目管理区:真源在 agent 的 global-memory.json。列表走 manage(review)(只读枚举,
|
|
847
|
+
不走 recall——recall 会更新访问统计并做会话去重,管理端轮询会污染召回数据);
|
|
848
|
+
编辑/删除走 store(agent 端负责向量懒补与 dropVectors);低价值淘汰走 manage(compact) -->
|
|
849
|
+
<div class="form-group memory-entries-section">
|
|
850
|
+
<div class="memory-entries-head">
|
|
851
|
+
<label data-i18n="memoryEntries.title">记忆条目</label>
|
|
852
|
+
<span id="memoryEntriesCount" class="memory-entries-count"></span>
|
|
853
|
+
<button class="toolbox-add-btn" type="button" id="memoryEntriesRefreshBtn" style="width:auto; margin:0; padding:6px 14px; border-style:solid;" data-i18n="memoryEntries.refreshBtn">刷新</button>
|
|
854
|
+
</div>
|
|
855
|
+
<div class="memory-entries-filter">
|
|
856
|
+
<input type="text" id="memoryEntriesSearch" data-i18n-placeholder="memoryEntries.searchPlaceholder" placeholder="">
|
|
857
|
+
<select id="memoryEntriesTypeFilter">
|
|
858
|
+
<option value="all" data-i18n="memoryEntries.filterAll">全部类型</option>
|
|
859
|
+
<option value="fact" data-i18n="memoryEntries.filterFact">事实</option>
|
|
860
|
+
<option value="summary" data-i18n="memoryEntries.filterSummary">摘要</option>
|
|
861
|
+
</select>
|
|
862
|
+
<!-- 排序规则直接暴露在选项文案里;纯前端对 review 全量重排,不发明新端点 -->
|
|
863
|
+
<select id="memoryEntriesSort">
|
|
864
|
+
<option value="createdDesc" data-i18n="memoryEntries.sortCreatedDesc">创建时间(新→旧)</option>
|
|
865
|
+
<option value="importanceDesc" data-i18n="memoryEntries.sortImportanceDesc">重要度(高→低)</option>
|
|
866
|
+
<option value="valueDesc" data-i18n="memoryEntries.sortValueDesc">价值分(高→低)</option>
|
|
867
|
+
<option value="valueAsc" data-i18n="memoryEntries.sortValueAsc">价值分(低→高)</option>
|
|
868
|
+
</select>
|
|
869
|
+
</div>
|
|
870
|
+
<div id="memoryEntriesCandidates" class="memory-entries-candidates" style="display:none;"></div>
|
|
871
|
+
<div id="memoryEntriesStatus" class="memory-entries-status" style="display:none;"></div>
|
|
872
|
+
<div id="memoryEntriesList" class="memory-entries-list"></div>
|
|
873
|
+
</div>
|
|
874
|
+
</div>
|
|
875
|
+
</div><!-- /tab-memory -->
|
|
876
|
+
|
|
740
877
|
<!-- 全局保存按钮(所有 tab 可见;内容超出一屏时吸附在视口底部,保证按钮始终可见) -->
|
|
741
878
|
<div class="save-bar" id="saveBar">
|
|
742
879
|
<button class="btn btn-save" id="saveBtn">
|
package/dist/side_panel.html
CHANGED
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title data-i18n="header.docTitle">AI Helper - 模型问答</title>
|
|
7
7
|
<script type="module" crossorigin src="/assets/rolldown-runtime-C0FnF6B9.js"></script>
|
|
8
|
-
<script type="module" crossorigin src="/assets/i18n-
|
|
9
|
-
<script type="module" crossorigin src="/assets/model-profiles-
|
|
10
|
-
<script type="module" crossorigin src="/assets/session-store-
|
|
11
|
-
<script type="module" crossorigin src="/assets/agent-store-
|
|
8
|
+
<script type="module" crossorigin src="/assets/i18n-C4jSRoCH.js"></script>
|
|
9
|
+
<script type="module" crossorigin src="/assets/model-profiles-Dl_MsXWa.js"></script>
|
|
10
|
+
<script type="module" crossorigin src="/assets/session-store-CzVQCgQ_.js"></script>
|
|
11
|
+
<script type="module" crossorigin src="/assets/agent-store-DcAs6YyA.js"></script>
|
|
12
|
+
<script type="module" crossorigin src="/assets/version-CNG2KGnJ.js"></script>
|
|
12
13
|
<script type="module" crossorigin src="/assets/side_panel.js"></script>
|
|
13
14
|
<script type="module" crossorigin src="/assets/modulepreload-polyfill-P2Xu9kJm.js"></script>
|
|
14
15
|
<link rel="stylesheet" crossorigin href="/assets/side_panel.css">
|
|
@@ -547,6 +548,10 @@
|
|
|
547
548
|
<div class="memory-limit-dropdown-title">
|
|
548
549
|
<span data-i18n="memory.historyLimit">记忆历史限制</span>
|
|
549
550
|
<span class="memory-limit-info-icon" data-i18n-title="memory.hint" title="记忆越多,上下文越连贯,但 token 消耗也越大;记忆太少,则可能无法结合前文准确回答。">ⓘ</span>
|
|
551
|
+
<!-- 跳配置页记忆 Tab 的快捷入口:与输入框 ✚ 面板的 MCP/Skill 管理入口同一形状同一通道 -->
|
|
552
|
+
<button class="memory-limit-config-entry" id="memoryLimitConfigEntry" type="button" data-i18n-title="memory.configEntry" title="打开记忆配置">
|
|
553
|
+
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg>
|
|
554
|
+
</button>
|
|
550
555
|
</div>
|
|
551
556
|
<div class="memory-limit-options" id="memoryLimitOptions">
|
|
552
557
|
<div class="memory-limit-option" data-value="0"><span class="memory-limit-check"></span><span data-i18n="memory.unlimited">不限制</span></div>
|
|
@@ -563,6 +568,24 @@
|
|
|
563
568
|
<input type="number" id="memoryLimitInput" min="1" max="400" placeholder="1-400" class="memory-limit-input">
|
|
564
569
|
<span class="memory-limit-custom-unit" data-i18n="memory.unit">条</span>
|
|
565
570
|
</div>
|
|
571
|
+
<div class="memory-extra-rows">
|
|
572
|
+
<div class="memory-switch-row">
|
|
573
|
+
<label class="memory-switch" for="autoCompactBtn">
|
|
574
|
+
<input type="checkbox" id="autoCompactBtn" style="display: none;">
|
|
575
|
+
<span class="toggle-switch"></span>
|
|
576
|
+
<span class="memory-switch-label" data-i18n="memory.autoCompactLabel">自动压缩</span>
|
|
577
|
+
</label>
|
|
578
|
+
<span class="memory-limit-info-icon" data-i18n-title="memory.autoCompactHint" title="上下文占用达到 85% 时,发送前自动压缩一次为摘要(会额外产生一次摘要调用)。">ⓘ</span>
|
|
579
|
+
</div>
|
|
580
|
+
<div class="memory-switch-row">
|
|
581
|
+
<label class="memory-switch" for="persistSummaryBtn">
|
|
582
|
+
<input type="checkbox" id="persistSummaryBtn" style="display: none;">
|
|
583
|
+
<span class="toggle-switch"></span>
|
|
584
|
+
<span class="memory-switch-label" data-i18n="memory.persistSummaryLabel">摘要存入记忆</span>
|
|
585
|
+
</label>
|
|
586
|
+
<span class="memory-limit-info-icon" data-i18n-title="memory.persistSummaryHint" title="压缩时把摘要沉淀为跨会话长期记忆;同一会话重复压缩只更新同一条。">ⓘ</span>
|
|
587
|
+
</div>
|
|
588
|
+
</div>
|
|
566
589
|
</div>
|
|
567
590
|
</div>
|
|
568
591
|
<div class="tool-toggle-wrapper">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-helper-agent",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "AI Helper Agent - Local file access and system command execution proxy for Chrome extension / AI Helper 代理服务,为 Chrome 扩展提供本地文件读写和系统命令执行",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/server.js",
|
package/src/config.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// agent/src/config.js - 配置管理(带内存缓存 + 写入互斥锁)
|
|
2
2
|
import { homedir } from 'os';
|
|
3
3
|
import { join } from 'path';
|
|
4
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync, statSync } from 'fs';
|
|
4
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, statSync, chmodSync } from 'fs';
|
|
5
5
|
import { t as translate } from './i18n.js';
|
|
6
|
+
import { encodeSecret, decodeSecret } from './crypto-lite.js';
|
|
6
7
|
|
|
7
8
|
const AGENT_DIR = join(homedir(), '.ai-helper-agent');
|
|
8
9
|
const CONFIG_FILE = join(AGENT_DIR, 'config.json');
|
|
@@ -21,7 +22,19 @@ const DEFAULTS = {
|
|
|
21
22
|
fileMaxSize: 50 * 1024 * 1024,
|
|
22
23
|
// RAG URL 导入内网访问策略:allowedPrivateHosts 非空时仅放行白名单命中(总开关不生效);
|
|
23
24
|
// 为空时由 allowPrivateNetwork 决定;缺省均关闭(默认拒绝内网),公网访问不受影响
|
|
24
|
-
ragUrlIngest: { allowPrivateNetwork: false, allowedPrivateHosts: [] }
|
|
25
|
+
ragUrlIngest: { allowPrivateNetwork: false, allowedPrivateHosts: [] },
|
|
26
|
+
// 长期记忆语义召回(hybrid recall)的 embedding 配置:缺省关闭 → recall 只走关键词通道(Tier A),
|
|
27
|
+
// 不引入任何新的默认网络调用。mode: 'openai-compat'(远端,零依赖)| 'local'(本地模型,需 RAG 依赖)。
|
|
28
|
+
// apiKey 只落本地 config.json,任何对外响应均经 mask 脱敏(hasApiKey 供前端提示“已配置”)。
|
|
29
|
+
memoryEmbedding: {
|
|
30
|
+
enabled: false,
|
|
31
|
+
mode: 'openai-compat',
|
|
32
|
+
baseUrl: '',
|
|
33
|
+
modelName: '',
|
|
34
|
+
apiKey: '',
|
|
35
|
+
dimensions: 0,
|
|
36
|
+
queryPrefix: ''
|
|
37
|
+
}
|
|
25
38
|
};
|
|
26
39
|
|
|
27
40
|
const VALID_CONFIG_KEYS = Object.keys(DEFAULTS);
|
|
@@ -51,14 +64,18 @@ function withWriteLock(fn) {
|
|
|
51
64
|
const prev = writeLock;
|
|
52
65
|
let release;
|
|
53
66
|
writeLock = new Promise(resolve => { release = resolve; });
|
|
54
|
-
|
|
67
|
+
// fn 允许返回非 Promise(同步写入体):统一用 Promise.resolve 兜住,
|
|
68
|
+
// 否则 fn().finally 直接 TypeError,且锁永远不会释放。
|
|
69
|
+
return prev
|
|
70
|
+
.then(() => Promise.resolve(fn()))
|
|
71
|
+
.finally(release);
|
|
55
72
|
}
|
|
56
73
|
|
|
57
74
|
function ensureAgentDir() {
|
|
58
75
|
try {
|
|
59
|
-
// 确保 Agent
|
|
76
|
+
// 确保 Agent 根目录存在(0700:仅当前用户可进入)
|
|
60
77
|
if (!existsSync(AGENT_DIR)) {
|
|
61
|
-
mkdirSync(AGENT_DIR, { recursive: true });
|
|
78
|
+
mkdirSync(AGENT_DIR, { recursive: true, mode: 0o700 });
|
|
62
79
|
}
|
|
63
80
|
// 确保 skills、workspace 和 memory 子目录存在
|
|
64
81
|
for (const dir of [SKILLS_DIR, WORKSPACE_DIR, MEMORY_DIR]) {
|
|
@@ -105,6 +122,10 @@ export function loadConfig() {
|
|
|
105
122
|
try {
|
|
106
123
|
const raw = readFileSync(CONFIG_FILE, 'utf-8');
|
|
107
124
|
const userConfig = JSON.parse(raw);
|
|
125
|
+
// 磁盘密文 → 内存明文(兼容旧版无 enc: 前缀的明文)
|
|
126
|
+
if (userConfig.memoryEmbedding && userConfig.memoryEmbedding.apiKey) {
|
|
127
|
+
userConfig.memoryEmbedding = { ...userConfig.memoryEmbedding, apiKey: decodeSecret(userConfig.memoryEmbedding.apiKey) };
|
|
128
|
+
}
|
|
108
129
|
configCache = { ...DEFAULTS, ...userConfig };
|
|
109
130
|
configCacheMtime = mtime;
|
|
110
131
|
} catch (err) {
|
|
@@ -135,26 +156,41 @@ function saveConfigSync(config) {
|
|
|
135
156
|
for (const key of VALID_CONFIG_KEYS) {
|
|
136
157
|
if (config[key] !== undefined) safe[key] = config[key];
|
|
137
158
|
}
|
|
138
|
-
|
|
159
|
+
// 落盘前混淆 apiKey(内存缓存保持明文)
|
|
160
|
+
if (safe.memoryEmbedding && safe.memoryEmbedding.apiKey) {
|
|
161
|
+
safe.memoryEmbedding = { ...safe.memoryEmbedding, apiKey: encodeSecret(safe.memoryEmbedding.apiKey) };
|
|
162
|
+
}
|
|
163
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(safe, null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
164
|
+
try { chmodSync(CONFIG_FILE, 0o600); } catch {} // 历史宽松文件收紧
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* 取锁后的实际写入体:落盘 + 刷新缓存(saveConfig 与 updateConfigField 共用)。
|
|
169
|
+
* 必须不加锁:两者都已在自己的 withWriteLock 内,再嵌一次会 自我等自我 而永久挂起。
|
|
170
|
+
*/
|
|
171
|
+
function writeConfigBody(config) {
|
|
172
|
+
try {
|
|
173
|
+
saveConfigSync(config);
|
|
174
|
+
// 从磁盘重新加载确保缓存与磁盘一致(解码后缓存保持明文)
|
|
175
|
+
const mtime = getMtime(CONFIG_FILE);
|
|
176
|
+
const raw = readFileSync(CONFIG_FILE, 'utf-8');
|
|
177
|
+
const parsed = JSON.parse(raw);
|
|
178
|
+
if (parsed.memoryEmbedding && parsed.memoryEmbedding.apiKey) {
|
|
179
|
+
parsed.memoryEmbedding = { ...parsed.memoryEmbedding, apiKey: decodeSecret(parsed.memoryEmbedding.apiKey) };
|
|
180
|
+
}
|
|
181
|
+
configCache = { ...DEFAULTS, ...parsed };
|
|
182
|
+
configCacheMtime = mtime;
|
|
183
|
+
} catch (err) {
|
|
184
|
+
console.error('[Config] Save config failed:', err.message);
|
|
185
|
+
throw err;
|
|
186
|
+
}
|
|
139
187
|
}
|
|
140
188
|
|
|
141
189
|
/**
|
|
142
190
|
* 保存配置(写入磁盘并更新缓存,加写锁防并发覆盖)
|
|
143
191
|
*/
|
|
144
192
|
export async function saveConfig(config) {
|
|
145
|
-
return withWriteLock(
|
|
146
|
-
try {
|
|
147
|
-
saveConfigSync(config);
|
|
148
|
-
// 从磁盘重新加载确保缓存与磁盘一致
|
|
149
|
-
const mtime = getMtime(CONFIG_FILE);
|
|
150
|
-
const raw = readFileSync(CONFIG_FILE, 'utf-8');
|
|
151
|
-
configCache = { ...DEFAULTS, ...JSON.parse(raw) };
|
|
152
|
-
configCacheMtime = mtime;
|
|
153
|
-
} catch (err) {
|
|
154
|
-
console.error('[Config] Save config failed:', err.message);
|
|
155
|
-
throw err;
|
|
156
|
-
}
|
|
157
|
-
});
|
|
193
|
+
return withWriteLock(() => writeConfigBody(config));
|
|
158
194
|
}
|
|
159
195
|
|
|
160
196
|
/**
|
|
@@ -165,9 +201,10 @@ export async function updateConfigField(key, value) {
|
|
|
165
201
|
throw new Error(translate(currentLang, 'error.invalidConfigKey', { key }));
|
|
166
202
|
}
|
|
167
203
|
return withWriteLock(async () => {
|
|
168
|
-
|
|
169
|
-
config[key]
|
|
170
|
-
|
|
204
|
+
// 浅拷贝后再改:loadConfig() 返回的是缓存本体,直接赋值会就地修改调用方已持有的对象
|
|
205
|
+
const config = { ...loadConfig(), [key]: value };
|
|
206
|
+
writeConfigBody(config);
|
|
207
|
+
return config;
|
|
171
208
|
});
|
|
172
209
|
}
|
|
173
210
|
|
|
@@ -189,7 +226,12 @@ export function loadPairings() {
|
|
|
189
226
|
}
|
|
190
227
|
|
|
191
228
|
try {
|
|
192
|
-
|
|
229
|
+
const parsed = JSON.parse(readFileSync(PAIRINGS_FILE, 'utf-8'));
|
|
230
|
+
// 磁盘密文 → 内存明文(兼容旧版)
|
|
231
|
+
for (const entry of Object.values(parsed)) {
|
|
232
|
+
if (entry.token) entry.token = decodeSecret(entry.token);
|
|
233
|
+
}
|
|
234
|
+
pairingsCache = parsed;
|
|
193
235
|
pairingsCacheMtime = mtime;
|
|
194
236
|
return pairingsCache;
|
|
195
237
|
} catch (err) {
|
|
@@ -209,7 +251,13 @@ export async function savePairing(extensionId, token) {
|
|
|
209
251
|
pairings[extensionId] = { token, pairedAt: Date.now() };
|
|
210
252
|
ensureAgentDir();
|
|
211
253
|
try {
|
|
212
|
-
|
|
254
|
+
// 落盘前混淆 token(内存缓存保持明文)
|
|
255
|
+
const diskPairings = {};
|
|
256
|
+
for (const [id, entry] of Object.entries(pairings)) {
|
|
257
|
+
diskPairings[id] = { ...entry, token: encodeSecret(entry.token) };
|
|
258
|
+
}
|
|
259
|
+
writeFileSync(PAIRINGS_FILE, JSON.stringify(diskPairings, null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
260
|
+
try { chmodSync(PAIRINGS_FILE, 0o600); } catch {} // 历史宽松文件收紧
|
|
213
261
|
pairingsCache = pairings;
|
|
214
262
|
pairingsCacheMtime = getMtime(PAIRINGS_FILE);
|
|
215
263
|
} catch (err) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// agent/src/crypto-lite.js - 密钥/Token 落盘混淆(Base64 + enc: 前缀标记)
|
|
2
|
+
// 非密码学安全,目的:避免配置文件被 cat/截图时一眼识别出密钥明文。
|
|
3
|
+
// 兼容规则:读取时若无 enc: 前缀视为旧明文原样返回;下次写入自动升级。
|
|
4
|
+
|
|
5
|
+
const PREFIX = 'enc:';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 编码:明文 → "enc:" + base64(明文)
|
|
9
|
+
* 空值原样返回(空字符串不编码)
|
|
10
|
+
* @param {string} plain
|
|
11
|
+
* @returns {string}
|
|
12
|
+
*/
|
|
13
|
+
export function encodeSecret(plain) {
|
|
14
|
+
if (!plain) return plain;
|
|
15
|
+
return PREFIX + Buffer.from(plain, 'utf-8').toString('base64');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 解码:"enc:..." → 明文;无 enc: 前缀 → 原样返回(兼容旧明文)
|
|
20
|
+
* @param {string} stored
|
|
21
|
+
* @returns {string}
|
|
22
|
+
*/
|
|
23
|
+
export function decodeSecret(stored) {
|
|
24
|
+
if (!stored) return stored;
|
|
25
|
+
if (!stored.startsWith(PREFIX)) return stored;
|
|
26
|
+
try {
|
|
27
|
+
return Buffer.from(stored.slice(PREFIX.length), 'base64').toString('utf-8');
|
|
28
|
+
} catch {
|
|
29
|
+
return stored; // 解码失败(损坏数据)→ 原样返回,不崩溃
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/locales/en.json
CHANGED
|
@@ -294,5 +294,60 @@
|
|
|
294
294
|
"gracefulShutdownTimeout": "Graceful shutdown timeout, force exit",
|
|
295
295
|
"uncaughtException": "Uncaught exception",
|
|
296
296
|
"unhandledRejection": "Unhandled Promise rejection"
|
|
297
|
+
},
|
|
298
|
+
"memory": {
|
|
299
|
+
"missingAction": "Missing action parameter",
|
|
300
|
+
"missingType": "Missing type parameter",
|
|
301
|
+
"invalidImportance": "importance must be a number between 1-10, received \"{value}\"",
|
|
302
|
+
"invalidMemoryType": "Invalid memoryType \"{type}\", only all / fact / summary are supported",
|
|
303
|
+
"invalidType": "Invalid memory type \"{type}\", only fact or summary are supported",
|
|
304
|
+
"missingContent": "Missing content parameter",
|
|
305
|
+
"readFailed": "Failed to read memory file: {error}",
|
|
306
|
+
"writeFailed": "Failed to write memory file: {error}",
|
|
307
|
+
"parseFailed": "Memory file parse failed: {error}",
|
|
308
|
+
"updatedDuplicate": "Updated existing memory: {id} (content identical, merged)",
|
|
309
|
+
"added": "Added memory: {id} ({type}){warning}",
|
|
310
|
+
"nearLimitWarning": "\n⚠️ Memory count approaching limit (facts: {facts}/{maxFacts}, summaries: {summaries}/{maxSummaries}). Consider calling agent_memory action=manage to review and clean up.",
|
|
311
|
+
"updateNeedsId": "update action requires memoryId parameter",
|
|
312
|
+
"deleteNeedsId": "delete action requires memoryId parameter",
|
|
313
|
+
"typeMismatch": "Memory {id} type mismatch (actual type: {type})",
|
|
314
|
+
"notFound": "Memory not found: {id}",
|
|
315
|
+
"updated": "Updated memory: {id}",
|
|
316
|
+
"deleted": "Deleted memory: {id}",
|
|
317
|
+
"unsupportedAction": "Unsupported action: {action}",
|
|
318
|
+
"storeMissingSubAction": "store action missing subAction (add/update/delete)",
|
|
319
|
+
"manageMissingSubAction": "manage action missing subAction (review/compact)",
|
|
320
|
+
"unsupportedMemoryAction": "Unsupported action: {action}",
|
|
321
|
+
"memoryEmpty": "Memory file is empty, no stored memories.",
|
|
322
|
+
"noMatch": "No matching memories found.",
|
|
323
|
+
"foundResults": "Found {count} relevant memories:{note}",
|
|
324
|
+
"recallNoteAllOld": "(Note: The above memories have already been retrieved in this conversation, returned again)",
|
|
325
|
+
"recallNoteMixed": "(Note: {oldCount} memories already retrieved in this conversation, only returning {newCount} new ones)",
|
|
326
|
+
"compactDone": "Memory compaction complete. Removed {facts} fact memories and {summaries} summary memories (value below {threshold}). Current: facts {totalFacts}, summaries {totalSummaries}",
|
|
327
|
+
"reviewTitle": "## Memory Review Report",
|
|
328
|
+
"reviewOverview": "**Overview**: Facts {facts}/{maxFacts}, Summaries {summaries}/{maxSummaries}",
|
|
329
|
+
"reviewLastReview": "**Last review**: {date}",
|
|
330
|
+
"reviewNeverReviewed": "Never reviewed",
|
|
331
|
+
"reviewCandidates": "**Low-value memory candidates** (value < {threshold}):",
|
|
332
|
+
"reviewNoCandidates": "No candidates for removal, all memories have good value.",
|
|
333
|
+
"reviewSuggestion": "**Suggested actions**:",
|
|
334
|
+
"reviewSuggestionDelete": "- For outdated/no longer applicable memories, use agent_memory action=store subAction=delete to remove",
|
|
335
|
+
"reviewSuggestionMerge": "- For similar memories, use agent_memory action=store subAction=update to merge",
|
|
336
|
+
"reviewSuggestionKeep": "- For still useful but low-value memories, keep them as-is",
|
|
337
|
+
"factLabel": "Fact",
|
|
338
|
+
"summaryLabel": "Summary",
|
|
339
|
+
"neverAccessed": "Never",
|
|
340
|
+
"memoryItemHeader": "**{index}. [{type}] {id}**",
|
|
341
|
+
"memoryItemContent": " Content: {content}",
|
|
342
|
+
"memoryItemTitle": " Title: {title}",
|
|
343
|
+
"memoryItemTags": " Tags: {tags}",
|
|
344
|
+
"memoryItemStats": " Importance: {importance}/10 | Access: {accessCount} times | Created: {createdAt}",
|
|
345
|
+
"reviewItemCreated": " Created: {createdAt} | Last access: {lastAccess} | Access: {accessCount} times",
|
|
346
|
+
"invalidEmbeddingMode": "Unsupported embedding mode, only openai-compat or local is allowed",
|
|
347
|
+
"embeddingDisabled": "Semantic recall is off, keyword channel only",
|
|
348
|
+
"embeddingTimeout": "Embedding service timed out, fell back to keyword recall",
|
|
349
|
+
"tierLabelNone": "keyword",
|
|
350
|
+
"tierLabelRemote": "remote vector + keyword",
|
|
351
|
+
"tierLabelLocal": "local vector + keyword"
|
|
297
352
|
}
|
|
298
353
|
}
|
package/src/locales/zh.json
CHANGED
|
@@ -294,5 +294,60 @@
|
|
|
294
294
|
"gracefulShutdownTimeout": "优雅关闭超时,强制退出",
|
|
295
295
|
"uncaughtException": "未捕获异常",
|
|
296
296
|
"unhandledRejection": "未处理的 Promise 拒绝"
|
|
297
|
+
},
|
|
298
|
+
"memory": {
|
|
299
|
+
"missingAction": "缺少 action 参数",
|
|
300
|
+
"missingType": "缺少 type 参数",
|
|
301
|
+
"invalidImportance": "importance 必须是 1-10 之间的数字,收到 \"{value}\"",
|
|
302
|
+
"invalidMemoryType": "memoryType \"{type}\" 无效,只支持 all / fact / summary",
|
|
303
|
+
"invalidType": "记忆类型 \"{type}\" 无效,只支持 fact 或 summary",
|
|
304
|
+
"missingContent": "缺少 content 参数",
|
|
305
|
+
"readFailed": "读取记忆文件失败: {error}",
|
|
306
|
+
"writeFailed": "写入记忆文件失败: {error}",
|
|
307
|
+
"parseFailed": "记忆文件解析失败: {error}",
|
|
308
|
+
"updatedDuplicate": "已更新已有记忆: {id}(内容相同,已合并)",
|
|
309
|
+
"added": "已添加记忆: {id} ({type}){warning}",
|
|
310
|
+
"nearLimitWarning": "\n⚠️ 记忆数量接近上限(事实: {facts}/{maxFacts}, 摘要: {summaries}/{maxSummaries}),建议调用 agent_memory action=manage 进行审查整理。",
|
|
311
|
+
"updateNeedsId": "update 操作需要 memoryId 参数",
|
|
312
|
+
"deleteNeedsId": "delete 操作需要 memoryId 参数",
|
|
313
|
+
"typeMismatch": "记忆 {id} 类型不匹配(实际类型: {type})",
|
|
314
|
+
"notFound": "未找到记忆: {id}",
|
|
315
|
+
"updated": "已更新记忆: {id}",
|
|
316
|
+
"deleted": "已删除记忆: {id}",
|
|
317
|
+
"unsupportedAction": "不支持的操作: {action}",
|
|
318
|
+
"storeMissingSubAction": "store 操作缺少 subAction (add/update/delete)",
|
|
319
|
+
"manageMissingSubAction": "manage 操作缺少 subAction (review/compact)",
|
|
320
|
+
"unsupportedMemoryAction": "不支持的 action: {action}",
|
|
321
|
+
"memoryEmpty": "记忆文件为空,暂无存储的记忆。",
|
|
322
|
+
"noMatch": "未找到匹配的记忆。",
|
|
323
|
+
"foundResults": "找到 {count} 条相关记忆:{note}",
|
|
324
|
+
"recallNoteAllOld": "(注:以上记忆在本对话中已检索过,此处为重复返回)",
|
|
325
|
+
"recallNoteMixed": "(注:{oldCount} 条记忆在本对话中已检索过,仅返回新的 {newCount} 条)",
|
|
326
|
+
"compactDone": "记忆压缩完成。移除了 {facts} 条事实记忆和 {summaries} 条摘要记忆(价值低于 {threshold})。当前: 事实 {totalFacts}, 摘要 {totalSummaries}",
|
|
327
|
+
"reviewTitle": "## 记忆审查报告",
|
|
328
|
+
"reviewOverview": "**概况**: 事实 {facts}/{maxFacts}, 摘要 {summaries}/{maxSummaries}",
|
|
329
|
+
"reviewLastReview": "**上次审查**: {date}",
|
|
330
|
+
"reviewNeverReviewed": "从未审查",
|
|
331
|
+
"reviewCandidates": "**低价值记忆候选** (价值 < {threshold}):",
|
|
332
|
+
"reviewNoCandidates": "无候选淘汰项,所有记忆价值良好。",
|
|
333
|
+
"reviewSuggestion": "**建议操作**:",
|
|
334
|
+
"reviewSuggestionDelete": "- 对于确实过时/不再适用的记忆,使用 agent_memory action=store subAction=delete 删除",
|
|
335
|
+
"reviewSuggestionMerge": "- 对于内容相似的记忆,使用 agent_memory action=store subAction=update 合并",
|
|
336
|
+
"reviewSuggestionKeep": "- 对于仍有用但价值低的记忆,可保留不做处理",
|
|
337
|
+
"factLabel": "事实",
|
|
338
|
+
"summaryLabel": "摘要",
|
|
339
|
+
"neverAccessed": "从未",
|
|
340
|
+
"memoryItemHeader": "**{index}. [{type}] {id}**",
|
|
341
|
+
"memoryItemContent": " 内容: {content}",
|
|
342
|
+
"memoryItemTitle": " 标题: {title}",
|
|
343
|
+
"memoryItemTags": " 标签: {tags}",
|
|
344
|
+
"memoryItemStats": " 重要性: {importance}/10 | 访问: {accessCount}次 | 创建: {createdAt}",
|
|
345
|
+
"reviewItemCreated": " 创建: {createdAt} | 最后访问: {lastAccess} | 访问: {accessCount}次",
|
|
346
|
+
"invalidEmbeddingMode": "不支持的 embedding 模式,仅支持 openai-compat 或 local",
|
|
347
|
+
"embeddingDisabled": "未启用语义召回,当前仅关键词通道",
|
|
348
|
+
"embeddingTimeout": "向量服务响应超时,本次已回落关键词召回",
|
|
349
|
+
"tierLabelNone": "关键词",
|
|
350
|
+
"tierLabelRemote": "远端向量+关键词",
|
|
351
|
+
"tierLabelLocal": "本地向量+关键词"
|
|
297
352
|
}
|
|
298
353
|
}
|