codexmate 0.0.44 → 0.0.47
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 +20 -7
- package/README.zh.md +20 -7
- package/cli.js +473 -3
- package/package.json +2 -1
- package/web-ui/app.js +42 -5
- package/web-ui/index.html +2 -0
- package/web-ui/modules/app.computed.index.mjs +3 -1
- package/web-ui/modules/app.computed.main-tabs.mjs +3 -0
- package/web-ui/modules/app.computed.prompts.mjs +28 -0
- package/web-ui/modules/app.computed.session.mjs +23 -1
- package/web-ui/modules/app.constants.mjs +13 -0
- package/web-ui/modules/app.methods.agents.mjs +50 -4
- package/web-ui/modules/app.methods.index.mjs +6 -0
- package/web-ui/modules/app.methods.navigation.mjs +2 -1
- package/web-ui/modules/app.methods.opencode-config.mjs +228 -0
- package/web-ui/modules/app.methods.session-actions.mjs +10 -0
- package/web-ui/modules/app.methods.startup-claude.mjs +3 -1
- package/web-ui/modules/app.methods.tool-config-permissions.mjs +3 -2
- package/web-ui/modules/config-mode.computed.mjs +17 -1
- package/web-ui/modules/i18n/locales/en.mjs +66 -5
- package/web-ui/modules/i18n/locales/ja.mjs +66 -5
- package/web-ui/modules/i18n/locales/vi.mjs +74 -0
- package/web-ui/modules/i18n/locales/zh-tw.mjs +1269 -0
- package/web-ui/modules/i18n/locales/zh.mjs +66 -5
- package/web-ui/modules/i18n.dict.mjs +2 -0
- package/web-ui/modules/i18n.mjs +3 -2
- package/web-ui/partials/index/layout-footer.html +1 -1
- package/web-ui/partials/index/layout-header.html +70 -2
- package/web-ui/partials/index/modal-config-template-agents.html +12 -13
- package/web-ui/partials/index/panel-config-claude.html +6 -12
- package/web-ui/partials/index/panel-config-codex.html +6 -11
- package/web-ui/partials/index/panel-config-opencode.html +166 -0
- package/web-ui/partials/index/panel-prompts.html +100 -0
- package/web-ui/partials/index/panel-sessions.html +30 -10
- package/web-ui/partials/index/panel-usage.html +34 -18
- package/web-ui/res/web-ui-render.precompiled.js +932 -183
- package/web-ui/styles/controls-forms.css +62 -4
- package/web-ui/styles/modals-core.css +162 -0
- package/web-ui/styles/responsive.css +65 -5
- package/web-ui/styles/sessions-toolbar-trash.css +45 -10
- package/web-ui/styles/sessions-usage.css +31 -2
|
@@ -151,6 +151,7 @@ export function createMainTabsComputed() {
|
|
|
151
151
|
if (this.mainTab === 'plugins') return this.t('kicker.plugins');
|
|
152
152
|
if (this.mainTab === 'docs') return this.t('kicker.docs');
|
|
153
153
|
if (this.mainTab === 'trash') return this.t('kicker.trash');
|
|
154
|
+
if (this.mainTab === 'prompts') return this.t('kicker.prompts');
|
|
154
155
|
return this.t('kicker.settings');
|
|
155
156
|
},
|
|
156
157
|
mainTabTitle() {
|
|
@@ -163,6 +164,7 @@ export function createMainTabsComputed() {
|
|
|
163
164
|
if (this.mainTab === 'plugins') return this.t('title.plugins');
|
|
164
165
|
if (this.mainTab === 'docs') return this.t('title.docs');
|
|
165
166
|
if (this.mainTab === 'trash') return this.t('settings.trash.title');
|
|
167
|
+
if (this.mainTab === 'prompts') return this.t('title.prompts');
|
|
166
168
|
return this.t('title.settings');
|
|
167
169
|
},
|
|
168
170
|
mainTabSubtitle() {
|
|
@@ -175,6 +177,7 @@ export function createMainTabsComputed() {
|
|
|
175
177
|
if (this.mainTab === 'plugins') return this.t('subtitle.plugins');
|
|
176
178
|
if (this.mainTab === 'docs') return this.t('subtitle.docs');
|
|
177
179
|
if (this.mainTab === 'trash') return this.t('settings.trash.meta');
|
|
180
|
+
if (this.mainTab === 'prompts') return this.t('subtitle.prompts');
|
|
178
181
|
return this.t('subtitle.settings');
|
|
179
182
|
},
|
|
180
183
|
taskOrchestrationSelectedRun() {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function createPromptsComputed() {
|
|
2
|
+
return {
|
|
3
|
+
promptsContextHint() {
|
|
4
|
+
if (!this.agentsLoading && !this.agentsDiffVisible && this.hasAgentsContentChanged()) {
|
|
5
|
+
return { text: this.t('modal.agents.unsaved.detectedHint'), warn: true };
|
|
6
|
+
}
|
|
7
|
+
if (this.agentsDiffVisible && (this.agentsDiffLoading || this.agentsSaving)) {
|
|
8
|
+
return { text: this.t('diff.hint.busy'), warn: false };
|
|
9
|
+
}
|
|
10
|
+
if (this.agentsDiffVisible && this.agentsDiffError) {
|
|
11
|
+
return { text: this.t('diff.hint.failedBack'), warn: false };
|
|
12
|
+
}
|
|
13
|
+
if (this.agentsDiffVisible && !this.agentsDiffHasChanges) {
|
|
14
|
+
return { text: this.t('diff.hint.noChangesBack'), warn: false };
|
|
15
|
+
}
|
|
16
|
+
if (this.agentsDiffVisible && this.agentsDiffTruncated) {
|
|
17
|
+
return { text: this.t('diff.viewHint.truncated'), warn: false };
|
|
18
|
+
}
|
|
19
|
+
if (this.agentsDiffVisible) {
|
|
20
|
+
return { text: this.t('diff.viewHint.preview'), warn: false };
|
|
21
|
+
}
|
|
22
|
+
if (!this.agentsLoading) {
|
|
23
|
+
return { text: this.t('modal.agents.hint.twoStepSave'), warn: false };
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -777,7 +777,7 @@ export function createSessionComputed() {
|
|
|
777
777
|
? this.sessionUsageDaily
|
|
778
778
|
: null;
|
|
779
779
|
if (!daily || !Array.isArray(daily.rows) || daily.rows.length === 0) {
|
|
780
|
-
return { points: [], labels: [], linePath: '', areaPath: '', width: 800, maxTokens: 0 };
|
|
780
|
+
return { points: [], labels: [], linePath: '', areaPath: '', width: 800, maxTokens: 0, yTicks: [] };
|
|
781
781
|
}
|
|
782
782
|
|
|
783
783
|
const rows = daily.rows;
|
|
@@ -806,6 +806,27 @@ export function createSessionComputed() {
|
|
|
806
806
|
const selectedKey = this.sessionsUsageSelectedDayKey;
|
|
807
807
|
const selectedPoint = points.find(p => p.key === selectedKey) || points[points.length - 1] || null;
|
|
808
808
|
|
|
809
|
+
const yTicks = [];
|
|
810
|
+
if (maxTokens > 0) {
|
|
811
|
+
const rawStep = maxTokens / 4;
|
|
812
|
+
const magnitude = Math.pow(10, Math.floor(Math.log10(rawStep)));
|
|
813
|
+
const residual = rawStep / magnitude;
|
|
814
|
+
const niceStep = residual <= 1.5 ? magnitude : (residual <= 3.5 ? 2 * magnitude : 5 * magnitude);
|
|
815
|
+
const tickCount = Math.min(Math.ceil(maxTokens / niceStep), 8);
|
|
816
|
+
for (let i = 0; i <= tickCount; i++) {
|
|
817
|
+
const value = i * niceStep;
|
|
818
|
+
if (value > maxTokens + niceStep * 0.5) break;
|
|
819
|
+
const normalized = value / maxTokens;
|
|
820
|
+
const y = padding.top + chartHeight - (normalized * chartHeight);
|
|
821
|
+
yTicks.push({
|
|
822
|
+
value,
|
|
823
|
+
label: formatCompactUsageSummaryNumber(value),
|
|
824
|
+
y,
|
|
825
|
+
percent: (((height - y) / height) * 100).toFixed(1)
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
809
830
|
return {
|
|
810
831
|
points,
|
|
811
832
|
labels: rows.map((row, index) => ({
|
|
@@ -817,6 +838,7 @@ export function createSessionComputed() {
|
|
|
817
838
|
width,
|
|
818
839
|
height,
|
|
819
840
|
maxTokens,
|
|
841
|
+
yTicks,
|
|
820
842
|
hoverX: selectedPoint ? selectedPoint.x : 0,
|
|
821
843
|
hoverY: selectedPoint ? selectedPoint.y : 0
|
|
822
844
|
};
|
|
@@ -2,6 +2,19 @@ export const SESSION_TRASH_LIST_LIMIT = 500;
|
|
|
2
2
|
export const SESSION_TRASH_PAGE_SIZE = 200;
|
|
3
3
|
export const DEFAULT_MODEL_CONTEXT_WINDOW = 190000;
|
|
4
4
|
export const DEFAULT_MODEL_AUTO_COMPACT_TOKEN_LIMIT = 185000;
|
|
5
|
+
export const OPENCODE_MODEL_CATALOG = Object.freeze({
|
|
6
|
+
anthropic: Object.freeze(['claude-4-sonnet', 'claude-4-opus', 'claude-3.7-sonnet', 'claude-3.5-sonnet']),
|
|
7
|
+
openai: Object.freeze(['gpt-4.1', 'gpt-4o', 'o4-mini', 'o3-mini']),
|
|
8
|
+
gemini: Object.freeze(['gemini-2.5-pro', 'gemini-2.5-flash', 'gemini-1.5-pro']),
|
|
9
|
+
groq: Object.freeze(['llama-3.3-70b-versatile', 'llama-3.1-8b-instant']),
|
|
10
|
+
openrouter: Object.freeze(['anthropic/claude-3.7-sonnet', 'openai/gpt-4.1', 'google/gemini-2.5-pro']),
|
|
11
|
+
copilot: Object.freeze(['gpt-4o', 'claude-3.7-sonnet']),
|
|
12
|
+
azure: Object.freeze(['gpt-4.1', 'gpt-4o']),
|
|
13
|
+
bedrock: Object.freeze(['anthropic.claude-3-7-sonnet-20250219-v1:0']),
|
|
14
|
+
vertexai: Object.freeze(['gemini-2.5-pro', 'gemini-2.5-flash']),
|
|
15
|
+
xai: Object.freeze(['grok-3', 'grok-3-mini']),
|
|
16
|
+
local: Object.freeze(['local.model'])
|
|
17
|
+
});
|
|
5
18
|
export const DEFAULT_OPENCLAW_TEMPLATE = `{
|
|
6
19
|
// OpenClaw config (JSON5)
|
|
7
20
|
agent: {
|
|
@@ -635,17 +635,63 @@ export function createAgentsMethods(options = {}) {
|
|
|
635
635
|
return;
|
|
636
636
|
}
|
|
637
637
|
const successLabel = this.agentsContext === 'openclaw-workspace'
|
|
638
|
-
?
|
|
638
|
+
? this.t('toast.agents.saved.workspace', { name: this.agentsWorkspaceFileName || '' }).replace(/:\s*$/, '')
|
|
639
639
|
: (this.agentsContext === 'claude-md'
|
|
640
|
-
? '
|
|
641
|
-
: (this.agentsContext === 'openclaw' ? '
|
|
640
|
+
? this.t('toast.agents.saved.claudeMd')
|
|
641
|
+
: (this.agentsContext === 'openclaw' ? this.t('toast.agents.saved.openclaw') : this.t('toast.agents.saved.agents')));
|
|
642
642
|
this.showMessage(successLabel, 'success');
|
|
643
|
-
this.
|
|
643
|
+
if (this.mainTab === 'prompts') {
|
|
644
|
+
this.loadPromptsContent();
|
|
645
|
+
} else {
|
|
646
|
+
this.closeAgentsModal({ force: true });
|
|
647
|
+
}
|
|
644
648
|
} catch (e) {
|
|
645
649
|
this.showMessage(this.t('toast.save.fail'), 'error');
|
|
646
650
|
} finally {
|
|
647
651
|
this.agentsSaving = false;
|
|
648
652
|
}
|
|
653
|
+
},
|
|
654
|
+
|
|
655
|
+
switchPromptsSubTab(subTab) {
|
|
656
|
+
const normalized = subTab === 'claude-md' ? 'claude-md' : 'codex';
|
|
657
|
+
if (this.promptsSubTab === normalized) {
|
|
658
|
+
this.loadPromptsContent();
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
this.promptsSubTab = normalized;
|
|
662
|
+
},
|
|
663
|
+
|
|
664
|
+
async loadPromptsContent() {
|
|
665
|
+
const requestToken = issueLatestRequestToken(this, '_agentsOpenRequestToken');
|
|
666
|
+
this.agentsLoading = true;
|
|
667
|
+
this.resetAgentsDiffState();
|
|
668
|
+
try {
|
|
669
|
+
const isClaude = this.promptsSubTab === 'claude-md';
|
|
670
|
+
const action = isClaude ? 'get-claude-md-file' : 'get-agents-file';
|
|
671
|
+
const res = await api(action);
|
|
672
|
+
if (!isLatestRequestToken(this, '_agentsOpenRequestToken', requestToken)) {
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
if (res.error) {
|
|
676
|
+
this.showMessage(res.error, 'error');
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
this.agentsContent = res.content || '';
|
|
680
|
+
this.agentsOriginalContent = this.agentsContent;
|
|
681
|
+
this.agentsPath = res.path || '';
|
|
682
|
+
this.agentsExists = !!res.exists;
|
|
683
|
+
this.agentsLineEnding = res.lineEnding === '\r\n' ? '\r\n' : '\n';
|
|
684
|
+
this.agentsContext = isClaude ? 'claude-md' : 'codex';
|
|
685
|
+
} catch (e) {
|
|
686
|
+
if (!isLatestRequestToken(this, '_agentsOpenRequestToken', requestToken)) {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
this.showMessage(this.t('toast.load.fail'), 'error');
|
|
690
|
+
} finally {
|
|
691
|
+
if (isLatestRequestToken(this, '_agentsOpenRequestToken', requestToken)) {
|
|
692
|
+
this.agentsLoading = false;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
649
695
|
}
|
|
650
696
|
};
|
|
651
697
|
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
DEFAULT_MODEL_AUTO_COMPACT_TOKEN_LIMIT,
|
|
8
8
|
DEFAULT_MODEL_CONTEXT_WINDOW,
|
|
9
9
|
DEFAULT_OPENCLAW_TEMPLATE,
|
|
10
|
+
OPENCODE_MODEL_CATALOG,
|
|
10
11
|
SESSION_TRASH_LIST_LIMIT,
|
|
11
12
|
SESSION_TRASH_PAGE_SIZE
|
|
12
13
|
} from './app.constants.mjs';
|
|
@@ -18,6 +19,7 @@ import { createNavigationMethods } from './app.methods.navigation.mjs';
|
|
|
18
19
|
import { createOpenclawCoreMethods } from './app.methods.openclaw-core.mjs';
|
|
19
20
|
import { createOpenclawEditingMethods } from './app.methods.openclaw-editing.mjs';
|
|
20
21
|
import { createOpenclawPersistMethods } from './app.methods.openclaw-persist.mjs';
|
|
22
|
+
import { createOpencodeConfigMethods } from './app.methods.opencode-config.mjs';
|
|
21
23
|
import { createProvidersMethods } from './app.methods.providers.mjs';
|
|
22
24
|
import { createRuntimeMethods } from './app.methods.runtime.mjs';
|
|
23
25
|
import { createToolConfigPermissionMethods } from './app.methods.tool-config-permissions.mjs';
|
|
@@ -89,6 +91,10 @@ export function createAppMethods() {
|
|
|
89
91
|
api,
|
|
90
92
|
defaultOpenclawTemplate: DEFAULT_OPENCLAW_TEMPLATE
|
|
91
93
|
}),
|
|
94
|
+
...createOpencodeConfigMethods({
|
|
95
|
+
api,
|
|
96
|
+
modelCatalog: OPENCODE_MODEL_CATALOG
|
|
97
|
+
}),
|
|
92
98
|
...createInstallMethods({ api }),
|
|
93
99
|
...createRuntimeMethods({ api }),
|
|
94
100
|
...createTaskOrchestrationMethods({ api })
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
function normalizeOpencodeProviderName(value) {
|
|
2
|
+
const name = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
3
|
+
return /^[a-z0-9_.-]+$/.test(name) ? name : '';
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function normalizeOpencodeAgentName(value) {
|
|
7
|
+
const name = typeof value === 'string' ? value.trim() : '';
|
|
8
|
+
return /^[a-zA-Z0-9_.-]+$/.test(name) ? name : '';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function getOpencodeJsonParser() {
|
|
12
|
+
const root = typeof globalThis !== 'undefined' ? globalThis : {};
|
|
13
|
+
const json5 = root.JSON5 || (root.window && root.window.JSON5);
|
|
14
|
+
if (json5 && typeof json5.parse === 'function') {
|
|
15
|
+
return json5;
|
|
16
|
+
}
|
|
17
|
+
return JSON;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function summarizeOpencodeDraft(content) {
|
|
21
|
+
const parsed = getOpencodeJsonParser().parse(content || '{}');
|
|
22
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
23
|
+
throw new Error('OpenCode config must be a JSON/JSONC object');
|
|
24
|
+
}
|
|
25
|
+
return parsed;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function createOpencodeConfigMethods(options = {}) {
|
|
29
|
+
const { api, modelCatalog = {} } = options;
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
opencodeProviderCatalog() {
|
|
33
|
+
const configured = Array.isArray(this.opencodeProviders)
|
|
34
|
+
? this.opencodeProviders.map(item => item && item.name).filter(Boolean)
|
|
35
|
+
: [];
|
|
36
|
+
const builtin = Object.keys(modelCatalog || {});
|
|
37
|
+
return [...new Set([...configured, ...builtin])].sort((a, b) => a.localeCompare(b));
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
opencodeModelCatalogForProvider(providerName = this.opencodeProvider) {
|
|
41
|
+
const provider = normalizeOpencodeProviderName(providerName);
|
|
42
|
+
const list = provider && Array.isArray(modelCatalog[provider]) ? modelCatalog[provider] : [];
|
|
43
|
+
const configured = Array.isArray(this.opencodeAgents)
|
|
44
|
+
? this.opencodeAgents.map(item => item && item.model).filter(Boolean)
|
|
45
|
+
: [];
|
|
46
|
+
return [...new Set([...list, ...configured])];
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
fillOpencodeProvider(provider) {
|
|
50
|
+
const name = normalizeOpencodeProviderName(provider);
|
|
51
|
+
if (!name) return;
|
|
52
|
+
this.opencodeProvider = name;
|
|
53
|
+
const models = this.opencodeModelCatalogForProvider(name);
|
|
54
|
+
if (!this.opencodeModel || !models.includes(this.opencodeModel)) {
|
|
55
|
+
this.opencodeModel = models[0] || '';
|
|
56
|
+
}
|
|
57
|
+
const selectedProvider = Array.isArray(this.opencodeProviders)
|
|
58
|
+
? this.opencodeProviders.find(item => normalizeOpencodeProviderName(item && item.name) === name)
|
|
59
|
+
: null;
|
|
60
|
+
this.opencodeProviderDisabled = !!(selectedProvider && selectedProvider.disabled);
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
refreshOpencodeSelectionFromSummary(res = {}) {
|
|
64
|
+
const providers = Array.isArray(res.providers) ? res.providers : [];
|
|
65
|
+
const agents = Array.isArray(res.agents) ? res.agents : [];
|
|
66
|
+
this.opencodeProviders = providers;
|
|
67
|
+
this.opencodeAgents = agents;
|
|
68
|
+
const currentAgent = normalizeOpencodeAgentName(res.currentAgent) || 'build';
|
|
69
|
+
this.opencodeAgent = currentAgent;
|
|
70
|
+
const currentModel = typeof res.currentModel === 'string' ? res.currentModel.trim() : '';
|
|
71
|
+
this.opencodeModel = currentModel;
|
|
72
|
+
if (typeof res.autoCompact === 'boolean') {
|
|
73
|
+
this.opencodeAutoCompact = res.autoCompact;
|
|
74
|
+
}
|
|
75
|
+
const enabledProvider = providers.find(item => item && item.disabled !== true && item.hasKey);
|
|
76
|
+
const firstProvider = providers.find(item => item && item.name);
|
|
77
|
+
this.opencodeProvider = normalizeOpencodeProviderName(res.currentProvider)
|
|
78
|
+
|| normalizeOpencodeProviderName(enabledProvider && enabledProvider.name)
|
|
79
|
+
|| normalizeOpencodeProviderName(firstProvider && firstProvider.name)
|
|
80
|
+
|| normalizeOpencodeProviderName(this.opencodeProvider)
|
|
81
|
+
|| 'anthropic';
|
|
82
|
+
const selectedProvider = providers.find(item => normalizeOpencodeProviderName(item && item.name) === this.opencodeProvider);
|
|
83
|
+
this.opencodeProviderDisabled = !!(selectedProvider && selectedProvider.disabled);
|
|
84
|
+
const models = this.opencodeModelCatalogForProvider(this.opencodeProvider);
|
|
85
|
+
if (!this.opencodeModel || (models.length && !models.includes(this.opencodeModel))) {
|
|
86
|
+
this.opencodeModel = models[0] || '';
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
async loadOpencodeConfig(options = {}) {
|
|
91
|
+
if (this.opencodeLoading) return;
|
|
92
|
+
this.opencodeLoading = true;
|
|
93
|
+
this.opencodeError = '';
|
|
94
|
+
try {
|
|
95
|
+
const res = await api('get-opencode-config');
|
|
96
|
+
if (res && res.error) {
|
|
97
|
+
this.opencodeError = res.error;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
this.opencodeContent = typeof res.content === 'string' ? res.content : '{}';
|
|
101
|
+
this.opencodeConfigPath = typeof res.targetPath === 'string' ? res.targetPath : '';
|
|
102
|
+
this.opencodeProviderStorePath = typeof res.providerStorePath === 'string' ? res.providerStorePath : '';
|
|
103
|
+
this.opencodeConfigExists = res.exists === true;
|
|
104
|
+
this.refreshOpencodeSelectionFromSummary(res || {});
|
|
105
|
+
if (options.toast === true) {
|
|
106
|
+
this.showMessage('OpenCode 配置已刷新', 'success');
|
|
107
|
+
}
|
|
108
|
+
} catch (e) {
|
|
109
|
+
this.opencodeError = e && e.message ? e.message : '读取 OpenCode 配置失败';
|
|
110
|
+
} finally {
|
|
111
|
+
this.opencodeLoading = false;
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
parseOpencodeImportContent(content, fileName = '') {
|
|
116
|
+
const raw = typeof content === 'string' ? content : '';
|
|
117
|
+
if (!raw.trim()) {
|
|
118
|
+
return { error: '导入文件为空' };
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const parsed = summarizeOpencodeDraft(raw);
|
|
122
|
+
const pretty = JSON.stringify(parsed, null, 2) + '\n';
|
|
123
|
+
return { content: pretty, fileName };
|
|
124
|
+
} catch (e) {
|
|
125
|
+
return { error: `OpenCode JSON/JSONC 解析失败: ${e.message}` };
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
async handleOpencodeImportChange(event) {
|
|
130
|
+
const input = event && event.target ? event.target : null;
|
|
131
|
+
const file = input && input.files && input.files[0] ? input.files[0] : null;
|
|
132
|
+
if (!file) return;
|
|
133
|
+
this.opencodeImportError = '';
|
|
134
|
+
try {
|
|
135
|
+
const content = await file.text();
|
|
136
|
+
const parsed = this.parseOpencodeImportContent(content, file.name || '');
|
|
137
|
+
if (parsed.error) {
|
|
138
|
+
this.opencodeImportError = parsed.error;
|
|
139
|
+
this.showMessage(parsed.error, 'error');
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
this.opencodeContent = parsed.content;
|
|
143
|
+
this.opencodeImportFileName = parsed.fileName;
|
|
144
|
+
this.showMessage('已解析 OpenCode 配置,确认后可保存', 'success');
|
|
145
|
+
} catch (e) {
|
|
146
|
+
this.opencodeImportError = e && e.message ? e.message : '读取导入文件失败';
|
|
147
|
+
this.showMessage(this.opencodeImportError, 'error');
|
|
148
|
+
} finally {
|
|
149
|
+
if (input) input.value = '';
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
async saveOpencodeConfig() {
|
|
154
|
+
if (this.opencodeSaving) return;
|
|
155
|
+
if (!this.isToolConfigWriteAllowed('opencode')) {
|
|
156
|
+
this.showMessage('请先打开 OpenCode 写入开关', 'error');
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
this.opencodeSaving = true;
|
|
160
|
+
this.opencodeError = '';
|
|
161
|
+
try {
|
|
162
|
+
const res = await api('apply-opencode-config', { content: this.opencodeContent });
|
|
163
|
+
if (res && res.error) {
|
|
164
|
+
this.opencodeError = res.error;
|
|
165
|
+
this.showMessage(res.error, 'error');
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
this.opencodeConfigExists = true;
|
|
169
|
+
if (res && typeof res.targetPath === 'string') this.opencodeConfigPath = res.targetPath;
|
|
170
|
+
if (res && typeof res.providerStorePath === 'string') this.opencodeProviderStorePath = res.providerStorePath;
|
|
171
|
+
this.refreshOpencodeSelectionFromSummary(res || {});
|
|
172
|
+
this.showMessage('OpenCode 配置已保存', 'success');
|
|
173
|
+
await this.loadOpencodeConfig();
|
|
174
|
+
} catch (e) {
|
|
175
|
+
this.opencodeError = e && e.message ? e.message : '保存 OpenCode 配置失败';
|
|
176
|
+
this.showMessage(this.opencodeError, 'error');
|
|
177
|
+
} finally {
|
|
178
|
+
this.opencodeSaving = false;
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
async applyOpencodeSelection() {
|
|
183
|
+
if (this.opencodeApplying) return;
|
|
184
|
+
if (!this.isToolConfigWriteAllowed('opencode')) {
|
|
185
|
+
this.showMessage('请先打开 OpenCode 写入开关', 'error');
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const provider = normalizeOpencodeProviderName(this.opencodeProvider);
|
|
189
|
+
const model = typeof this.opencodeModel === 'string' ? this.opencodeModel.trim() : '';
|
|
190
|
+
if (!provider || !model) {
|
|
191
|
+
this.showMessage('请选择 OpenCode provider 和 model', 'error');
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
this.opencodeApplying = true;
|
|
195
|
+
this.opencodeError = '';
|
|
196
|
+
try {
|
|
197
|
+
const res = await api('update-opencode-selection', {
|
|
198
|
+
provider,
|
|
199
|
+
model,
|
|
200
|
+
apiKey: this.opencodeApiKey,
|
|
201
|
+
agent: this.opencodeAgent || 'build',
|
|
202
|
+
applyToCoreAgents: this.opencodeApplyToCoreAgents === true,
|
|
203
|
+
disabled: this.opencodeProviderDisabled === true,
|
|
204
|
+
autoCompact: this.opencodeAutoCompact !== false,
|
|
205
|
+
maxTokens: this.opencodeMaxTokens,
|
|
206
|
+
reasoningEffort: this.opencodeReasoningEffort
|
|
207
|
+
});
|
|
208
|
+
if (res && res.error) {
|
|
209
|
+
this.opencodeError = res.error;
|
|
210
|
+
this.showMessage(res.error, 'error');
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (res && typeof res.content === 'string') this.opencodeContent = res.content;
|
|
214
|
+
if (res && typeof res.targetPath === 'string') this.opencodeConfigPath = res.targetPath;
|
|
215
|
+
if (res && typeof res.providerStorePath === 'string') this.opencodeProviderStorePath = res.providerStorePath;
|
|
216
|
+
this.opencodeConfigExists = true;
|
|
217
|
+
this.opencodeApiKey = '';
|
|
218
|
+
this.refreshOpencodeSelectionFromSummary(res || {});
|
|
219
|
+
this.showMessage('OpenCode provider/model 已应用', 'success');
|
|
220
|
+
} catch (e) {
|
|
221
|
+
this.opencodeError = e && e.message ? e.message : '应用 OpenCode 配置失败';
|
|
222
|
+
this.showMessage(this.opencodeError, 'error');
|
|
223
|
+
} finally {
|
|
224
|
+
this.opencodeApplying = false;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
@@ -90,6 +90,10 @@ export function createSessionActionMethods(options = {}) {
|
|
|
90
90
|
this.loadSessionStandalonePlain();
|
|
91
91
|
},
|
|
92
92
|
|
|
93
|
+
canBuildStandaloneUrl(session) {
|
|
94
|
+
return !!this.buildSessionStandaloneUrl(session);
|
|
95
|
+
},
|
|
96
|
+
|
|
93
97
|
buildSessionStandaloneUrl(session) {
|
|
94
98
|
if (!session) return '';
|
|
95
99
|
const source = typeof session.source === 'string' ? session.source.trim().toLowerCase() : '';
|
|
@@ -129,6 +133,12 @@ export function createSessionActionMethods(options = {}) {
|
|
|
129
133
|
this.showMessage(this.t('toast.copy.fail'), 'error');
|
|
130
134
|
},
|
|
131
135
|
|
|
136
|
+
openSessionLink(session) {
|
|
137
|
+
const url = this.buildSessionStandaloneUrl(session);
|
|
138
|
+
if (!url) { this.showMessage(this.t('toast.link.fail'), 'error'); return; }
|
|
139
|
+
window.open(url, '_blank', 'noopener,noreferrer');
|
|
140
|
+
},
|
|
141
|
+
|
|
132
142
|
getSessionFilePath(session) {
|
|
133
143
|
const filePath = typeof session?.filePath === 'string' ? session.filePath.trim() : '';
|
|
134
144
|
return filePath;
|
|
@@ -125,7 +125,8 @@ export function createStartupClaudeMethods(options = {}) {
|
|
|
125
125
|
if (statusRes.toolConfigPermissions && typeof statusRes.toolConfigPermissions === 'object') {
|
|
126
126
|
this.toolConfigPermissions = {
|
|
127
127
|
codex: statusRes.toolConfigPermissions.codex === true,
|
|
128
|
-
claude: statusRes.toolConfigPermissions.claude === true
|
|
128
|
+
claude: statusRes.toolConfigPermissions.claude === true,
|
|
129
|
+
opencode: statusRes.toolConfigPermissions.opencode === true
|
|
129
130
|
};
|
|
130
131
|
try {
|
|
131
132
|
localStorage.setItem('toolConfigPermissions', JSON.stringify(this.toolConfigPermissions));
|
|
@@ -134,6 +135,7 @@ export function createStartupClaudeMethods(options = {}) {
|
|
|
134
135
|
this.providersList = listRes.providers;
|
|
135
136
|
if (typeof this.loadLocalBridgeExcluded === 'function') { this.loadLocalBridgeExcluded(); }
|
|
136
137
|
if (typeof this.loadClaudeLocalBridgeStatus === 'function') { this.loadClaudeLocalBridgeStatus(); }
|
|
138
|
+
if (typeof this.loadOpencodeConfig === 'function') { this.loadOpencodeConfig(); }
|
|
137
139
|
if (statusRes.configReady === false) {
|
|
138
140
|
this.showMessage('配置已加载', 'info');
|
|
139
141
|
}
|
|
@@ -3,14 +3,15 @@ export function createToolConfigPermissionMethods(options = {}) {
|
|
|
3
3
|
|
|
4
4
|
function normalizeTarget(value) {
|
|
5
5
|
const target = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
6
|
-
return target === 'codex' || target === 'claude' ? target : '';
|
|
6
|
+
return target === 'codex' || target === 'claude' || target === 'opencode' ? target : '';
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
function normalizePermissions(value) {
|
|
10
10
|
const source = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
11
11
|
return {
|
|
12
12
|
codex: source.codex === true,
|
|
13
|
-
claude: source.claude === true
|
|
13
|
+
claude: source.claude === true,
|
|
14
|
+
opencode: source.opencode === true
|
|
14
15
|
};
|
|
15
16
|
}
|
|
16
17
|
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
export const CONFIG_MODE_SET = new Set([
|
|
11
11
|
...Object.keys(PROVIDER_CONFIG_MODE_META),
|
|
12
12
|
'claude',
|
|
13
|
-
'openclaw'
|
|
13
|
+
'openclaw',
|
|
14
|
+
'opencode'
|
|
14
15
|
]);
|
|
15
16
|
|
|
16
17
|
export function getProviderConfigModeMeta(mode) {
|
|
@@ -61,6 +62,7 @@ export function createConfigModeComputed() {
|
|
|
61
62
|
if (providerMeta) return providerMeta.label;
|
|
62
63
|
if (this.configMode === 'claude') return 'Claude Code';
|
|
63
64
|
if (this.configMode === 'openclaw') return 'OpenClaw';
|
|
65
|
+
if (this.configMode === 'opencode') return 'OpenCode';
|
|
64
66
|
return '未选择';
|
|
65
67
|
},
|
|
66
68
|
inspectorCurrentConfigLabel() {
|
|
@@ -76,6 +78,10 @@ export function createConfigModeComputed() {
|
|
|
76
78
|
const openclaw = typeof this.currentOpenclawConfig === 'string' ? this.currentOpenclawConfig.trim() : '';
|
|
77
79
|
return openclaw || '未选择';
|
|
78
80
|
}
|
|
81
|
+
if (this.configMode === 'opencode') {
|
|
82
|
+
const provider = typeof this.opencodeProvider === 'string' ? this.opencodeProvider.trim() : '';
|
|
83
|
+
return provider || '未选择';
|
|
84
|
+
}
|
|
79
85
|
return '未选择';
|
|
80
86
|
},
|
|
81
87
|
inspectorCurrentModelLabel() {
|
|
@@ -93,6 +99,10 @@ export function createConfigModeComputed() {
|
|
|
93
99
|
: '';
|
|
94
100
|
return model || '按配置文件';
|
|
95
101
|
}
|
|
102
|
+
if (this.configMode === 'opencode') {
|
|
103
|
+
const model = typeof this.opencodeModel === 'string' ? this.opencodeModel.trim() : '';
|
|
104
|
+
return model || '未选择';
|
|
105
|
+
}
|
|
96
106
|
return '未选择';
|
|
97
107
|
},
|
|
98
108
|
inspectorTemplateStatus() {
|
|
@@ -118,6 +128,12 @@ export function createConfigModeComputed() {
|
|
|
118
128
|
}
|
|
119
129
|
return 'JSON5 可保存并应用';
|
|
120
130
|
}
|
|
131
|
+
if (this.configMode === 'opencode') {
|
|
132
|
+
if (this.opencodeSaving || this.opencodeApplying) {
|
|
133
|
+
return 'OpenCode 保存/应用中';
|
|
134
|
+
}
|
|
135
|
+
return 'JSON 可编辑并应用';
|
|
136
|
+
}
|
|
121
137
|
return '未选择';
|
|
122
138
|
}
|
|
123
139
|
};
|