Skip to content

Commit fc2b9a6

Browse files
committed
feat(openclaw): gate config/workspace writes behind write toggle
Bring OpenClaw tab in line with codex/claude/opencode/kilocode: add an 'allow write' toggle at the top of the panel that gates all OpenClaw backend write actions behind the shared tool-config write guard, defaulting to read-only. Backend (cli.js) - TOOL_CONFIG_PERMISSION_TARGETS / DEFAULTS: add 'openclaw' - normalizeToolConfigPermissions: include openclaw - getApiToolConfigWriteTarget: classify apply-openclaw-config, apply-openclaw-agents-file, apply-openclaw-workspace-file as openclaw writes so the unified guard can deny them when locked Frontend - app.methods.tool-config-permissions: normalizeTarget + normalizePermissions accept 'openclaw' - app.js: toolConfigPermissions / toolConfigPermissionSaving state seeded with openclaw:false - panel-config-openclaw.html: insert tool-config-write-card at top, wrap workspace + configs in tool-config-write-scope (locked class), disable editor-open buttons + config edit/apply when write is off; delete-from-list button stays unguarded (only touches web-ui prefs) - i18n: add toolConfig.openclaw.{title,desc,confirmMessage} across en/zh/zh-tw/ja/vi, modelled on the kilocode set - regenerate web-ui-render.precompiled.js from updated template
1 parent 651b6f9 commit fc2b9a6

10 files changed

Lines changed: 204 additions & 133 deletions

File tree

cli.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -909,8 +909,8 @@ function isPlainObject(value) {
909909
return !!value && typeof value === 'object' && !Array.isArray(value);
910910
}
911911

912-
const TOOL_CONFIG_PERMISSION_TARGETS = new Set(['codex', 'claude', 'opencode', 'kilocode']);
913-
const TOOL_CONFIG_PERMISSION_DEFAULTS = Object.freeze({ codex: false, claude: false, opencode: false, kilocode: false });
912+
const TOOL_CONFIG_PERMISSION_TARGETS = new Set(['codex', 'claude', 'opencode', 'kilocode', 'openclaw']);
913+
const TOOL_CONFIG_PERMISSION_DEFAULTS = Object.freeze({ codex: false, claude: false, opencode: false, kilocode: false, openclaw: false });
914914
let toolConfigWriteGuardDepth = 0;
915915

916916
function enterToolConfigWriteGuard() {
@@ -938,7 +938,8 @@ function normalizeToolConfigPermissions(value) {
938938
codex: source.codex === true,
939939
claude: source.claude === true,
940940
opencode: source.opencode === true,
941-
kilocode: source.kilocode === true
941+
kilocode: source.kilocode === true,
942+
openclaw: source.openclaw === true
942943
};
943944
}
944945

@@ -1197,10 +1198,16 @@ function getApiToolConfigWriteTarget(action) {
11971198
'apply-kilocode-config',
11981199
'start-kilocode'
11991200
]);
1201+
const openclawWriteActions = new Set([
1202+
'apply-openclaw-config',
1203+
'apply-openclaw-agents-file',
1204+
'apply-openclaw-workspace-file'
1205+
]);
12001206
if (codexWriteActions.has(name)) return 'codex';
12011207
if (claudeWriteActions.has(name)) return 'claude';
12021208
if (opencodeWriteActions.has(name)) return 'opencode';
12031209
if (kilocodeWriteActions.has(name)) return 'kilocode';
1210+
if (openclawWriteActions.has(name)) return 'openclaw';
12041211
return '';
12051212
}
12061213

web-ui/app.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,8 +396,8 @@ document.addEventListener('DOMContentLoaded', () => {
396396
providerCacheError: '',
397397
providerCacheRequestSeq: 0,
398398
settingsTab: 'general',
399-
toolConfigPermissions: { codex: false, claude: false, opencode: false, kilocode: false },
400-
toolConfigPermissionSaving: { codex: false, claude: false, opencode: false, kilocode: false },
399+
toolConfigPermissions: { codex: false, claude: false, opencode: false, kilocode: false, openclaw: false },
400+
toolConfigPermissionSaving: { codex: false, claude: false, opencode: false, kilocode: false, openclaw: false },
401401
sessionTrashEnabled: true,
402402
sessionTrashItems: [],
403403
sessionTrashVisibleCount: SESSION_TRASH_PAGE_SIZE,

web-ui/modules/app.methods.tool-config-permissions.mjs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ export function createToolConfigPermissionMethods(options = {}) {
33

44
function normalizeTarget(value) {
55
const target = typeof value === 'string' ? value.trim().toLowerCase() : '';
6-
return target === 'codex' || target === 'claude' || target === 'opencode' || target === 'kilocode' ? target : '';
6+
return target === 'codex' || target === 'claude' || target === 'opencode' || target === 'kilocode' || target === 'openclaw' ? target : '';
77
}
88

99
function normalizePermissions(value) {
@@ -12,7 +12,8 @@ export function createToolConfigPermissionMethods(options = {}) {
1212
codex: source.codex === true,
1313
claude: source.claude === true,
1414
opencode: source.opencode === true,
15-
kilocode: source.kilocode === true
15+
kilocode: source.kilocode === true,
16+
openclaw: source.openclaw === true
1617
};
1718
}
1819

web-ui/modules/i18n/locales/en.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1536,6 +1536,9 @@ const en = Object.freeze({
15361536
'toolConfig.kilocode.lockedTitle': 'Read-only mode',
15371537
'toolConfig.kilocode.lockedDesc': 'Enable write access to auto-sync config or launch KiloCode.',
15381538
'toolConfig.kilocode.confirmMessage': 'When enabled, actions in the KiloCode tab can write ~/.config/kilo/kilo.jsonc or an existing kilo.json, and can launch local KiloCode.',
1539+
'toolConfig.openclaw.title': 'OpenClaw config writes',
1540+
'toolConfig.openclaw.desc': 'Read-only by default; enable to apply OpenClaw configs and write AGENTS.md and other workspace files.',
1541+
'toolConfig.openclaw.confirmMessage': 'When enabled, actions in the OpenClaw tab can write openclaw.json and OpenClaw workspace files (AGENTS.md, SOUL.md, etc.).',
15391542
'kilocode.providerModel.title': 'KiloCode provider / model',
15401543
'kilocode.connection.title': 'KiloCode connection',
15411544
'kilocode.targetFile': 'Active KiloCode config: {path} · {status}',

web-ui/modules/i18n/locales/ja.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1523,6 +1523,9 @@ const ja = Object.freeze({
15231523
'toolConfig.kilocode.lockedTitle': '読み取り専用モード',
15241524
'toolConfig.kilocode.lockedDesc': '書き込み権限を有効化すると、設定の自動同期または KiloCode の起動ができます。',
15251525
'toolConfig.kilocode.confirmMessage': '有効化すると、KiloCode タブ内の操作が ~/.config/kilo/kilo.jsonc または既存の kilo.json に書き込み、ローカル KiloCode を起動できます。',
1526+
'toolConfig.openclaw.title': 'OpenClaw 設定の書き込み',
1527+
'toolConfig.openclaw.desc': '既定は読み取り専用。有効化すると OpenClaw 設定を適用し、AGENTS.md などのワークスペースファイルを書き込みできます。',
1528+
'toolConfig.openclaw.confirmMessage': '有効化すると、OpenClaw タブ内の操作が openclaw.json およびワークスペースファイル(AGENTS.md、SOUL.md など)に書き込みできます。',
15261529
'kilocode.providerModel.title': 'KiloCode プロバイダー / モデル',
15271530
'kilocode.connection.title': 'KiloCode 接続',
15281531
'kilocode.targetFile': '有効な KiloCode 設定:{path} · {status}',

web-ui/modules/i18n/locales/vi.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1508,6 +1508,9 @@ const vi = Object.freeze({
15081508
'toolConfig.kilocode.lockedTitle': 'Chế độ chỉ đọc',
15091509
'toolConfig.kilocode.lockedDesc': 'Bật quyền ghi để tự động đồng bộ cấu hình hoặc khởi chạy KiloCode.',
15101510
'toolConfig.kilocode.confirmMessage': 'Khi bật, các thao tác trong tab KiloCode có thể ghi ~/.config/kilo/kilo.jsonc hoặc kilo.json hiện có, và có thể khởi chạy KiloCode cục bộ.',
1511+
'toolConfig.openclaw.title': 'Ghi cấu hình OpenClaw',
1512+
'toolConfig.openclaw.desc': 'Mặc định chỉ đọc; bật để áp dụng cấu hình OpenClaw và ghi AGENTS.md cùng các tệp workspace khác.',
1513+
'toolConfig.openclaw.confirmMessage': 'Khi bật, các thao tác trong tab OpenClaw có thể ghi openclaw.json và các tệp workspace (AGENTS.md, SOUL.md, v.v.).',
15111514
'kilocode.providerModel.title': 'Nhà cung cấp / mô hình KiloCode',
15121515
'kilocode.connection.title': 'Kết nối KiloCode',
15131516
'kilocode.targetFile': 'Cấu hình KiloCode đang dùng: {path} · {status}',

web-ui/modules/i18n/locales/zh-tw.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1536,6 +1536,9 @@ const zhTw = Object.freeze({
15361536
'toolConfig.kilocode.lockedTitle': '唯讀模式',
15371537
'toolConfig.kilocode.lockedDesc': '啟用寫入權限後可自動同步設定或啟動 KiloCode。',
15381538
'toolConfig.kilocode.confirmMessage': '開啟後,KiloCode tab 內的操作會寫入 ~/.config/kilo/kilo.jsonc 或既有的 kilo.json,並可啟動本機 KiloCode。',
1539+
'toolConfig.openclaw.title': 'OpenClaw 設定寫入',
1540+
'toolConfig.openclaw.desc': '預設唯讀;啟用後可套用 OpenClaw 設定並寫入 AGENTS.md 與其他工作區檔案。',
1541+
'toolConfig.openclaw.confirmMessage': '開啟後,OpenClaw tab 內的操作會寫入 openclaw.json 及 OpenClaw 工作區檔案(AGENTS.md、SOUL.md 等)。',
15391542
'kilocode.providerModel.title': 'KiloCode provider / 模型',
15401543
'kilocode.connection.title': 'KiloCode 連線',
15411544
'kilocode.targetFile': 'KiloCode 生效設定:{path} · {status}',

web-ui/modules/i18n/locales/zh.mjs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1536,6 +1536,9 @@ const zh = Object.freeze({
15361536
'toolConfig.kilocode.lockedTitle': '只读模式',
15371537
'toolConfig.kilocode.lockedDesc': '开启写入权限后可保存配置或启动 KiloCode。',
15381538
'toolConfig.kilocode.confirmMessage': '打开后,KiloCode tab 内的操作会写入 ~/.config/kilo/kilo.jsonc 或已存在的 kilo.json,并可启动本机 KiloCode。',
1539+
'toolConfig.openclaw.title': 'OpenClaw 配置写入',
1540+
'toolConfig.openclaw.desc': '默认只读;开启后可应用 OpenClaw 配置、写入 AGENTS.md 与其他工作区文件。',
1541+
'toolConfig.openclaw.confirmMessage': '打开后,OpenClaw tab 内的操作会写入 openclaw.json 及 OpenClaw 工作区文件(AGENTS.md、SOUL.md 等)。',
15391542
'kilocode.providerModel.title': 'KiloCode provider / model',
15401543
'kilocode.connection.title': 'KiloCode 连接',
15411544
'kilocode.targetFile': 'KiloCode 生效配置:{path} · {status}',

web-ui/partials/index/panel-config-openclaw.html

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,23 @@
1111
<button type="button" :class="['segment', { active: configMode === 'openclaw' }]" @click="switchConfigMode('openclaw')">{{ t('tab.config.openclaw') }}</button>
1212
</div>
1313
<div class="openclaw-layout">
14+
<section class="tool-config-write-card" :aria-label="t('toolConfig.openclaw.title')">
15+
<div class="tool-config-write-copy">
16+
<div class="tool-config-write-title">{{ t('toolConfig.openclaw.title') }}</div>
17+
<p class="tool-config-write-desc">{{ t('toolConfig.openclaw.desc') }}</p>
18+
</div>
19+
<label class="settings-toggle-row tool-config-write-toggle">
20+
<input
21+
type="checkbox"
22+
autocomplete="off"
23+
:checked="isToolConfigWriteAllowed('openclaw')"
24+
:disabled="toolConfigPermissionSaving.openclaw"
25+
@change="setToolConfigPermission('openclaw', $event.target.checked)">
26+
<span class="toggle-track"><span class="toggle-thumb"></span></span>
27+
<span>{{ toolConfigPermissionStatusLabel('openclaw') }}</span>
28+
</label>
29+
</section>
30+
1431
<section class="settings-card settings-card--wide openclaw-summary-card" aria-label="OpenClaw 当前摘要">
1532
<div class="settings-card-body">
1633
<div class="openclaw-summary-header">
@@ -32,10 +49,11 @@
3249
</div>
3350
</section>
3451

52+
<div class="tool-config-write-scope" :class="{ locked: !isToolConfigWriteAllowed('openclaw') }">
3553
<section class="settings-card settings-card--wide openclaw-workspace-card">
3654
<div class="settings-card-body">
3755
<div class="openclaw-tools-grid">
38-
<button class="openclaw-tool-btn" @click="openOpenclawAgentsEditor" :disabled="loading || !!initError || agentsLoading">
56+
<button class="openclaw-tool-btn" @click="openOpenclawAgentsEditor" :disabled="loading || !!initError || agentsLoading || !isToolConfigWriteAllowed('openclaw')">
3957
<div class="tool-icon">📄</div>
4058
<div class="tool-content">
4159
<div class="tool-title">AGENTS.md</div>
@@ -52,7 +70,7 @@
5270
type="button"
5371
class="openclaw-tool-btn"
5472
@click="openOpenclawQuickWorkspaceFile(fileName)"
55-
:disabled="loading || !!initError || agentsLoading">
73+
:disabled="loading || !!initError || agentsLoading || !isToolConfigWriteAllowed('openclaw')">
5674
<div class="tool-icon">📄</div>
5775
<div class="tool-content">
5876
<div class="tool-title">{{ fileName }}</div>
@@ -70,13 +88,14 @@
7088
<div class="settings-card-body">
7189
<div class="card-list openclaw-card-list">
7290
<div v-for="(config, name) in openclawConfigs" :key="name"
73-
:class="['card', { active: currentOpenclawConfig === name }]"
74-
@click="applyOpenclawConfig(name)"
75-
@keydown.enter.self.prevent="applyOpenclawConfig(name)"
76-
@keydown.space.self.prevent="applyOpenclawConfig(name)"
77-
tabindex="0"
91+
:class="['card', { active: currentOpenclawConfig === name, disabled: !isToolConfigWriteAllowed('openclaw') }]"
92+
@click="isToolConfigWriteAllowed('openclaw') && applyOpenclawConfig(name)"
93+
@keydown.enter.self.prevent="isToolConfigWriteAllowed('openclaw') && applyOpenclawConfig(name)"
94+
@keydown.space.self.prevent="isToolConfigWriteAllowed('openclaw') && applyOpenclawConfig(name)"
95+
:tabindex="isToolConfigWriteAllowed('openclaw') ? 0 : -1"
7896
role="button"
7997
:aria-label="t('openclaw.action.applyAria', { name })"
98+
:aria-disabled="!isToolConfigWriteAllowed('openclaw') ? 'true' : null"
8099
:aria-current="currentOpenclawConfig === name ? 'true' : null">
81100
<div class="card-leading">
82101
<div class="card-icon">{{ name.charAt(0).toUpperCase() }}</div>
@@ -95,7 +114,7 @@
95114
{{ openclawHasContent(config) ? t('openclaw.configured') : t('openclaw.notConfigured') }}
96115
</span>
97116
<div class="card-actions" @click.stop>
98-
<button class="card-action-btn" @click="openOpenclawEditModal(name)" :aria-label="t('openclaw.action.editAria', { name })" :title="t('openclaw.action.edit')">
117+
<button class="card-action-btn" @click="openOpenclawEditModal(name)" :disabled="!isToolConfigWriteAllowed('openclaw')" :aria-label="t('openclaw.action.editAria', { name })" :title="t('openclaw.action.edit')">
99118
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
100119
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
101120
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
@@ -113,5 +132,6 @@
113132
</div>
114133
</div>
115134
</section>
135+
</div>
116136
</div>
117137
</div>

0 commit comments

Comments
 (0)