codexmate 0.1.3 → 0.1.5

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.
@@ -204,6 +204,17 @@ function extractMessageFromRecord(record, source) {
204
204
  }
205
205
  return null;
206
206
  }
207
+ if (source === 'pi') {
208
+ if (record.type === 'message' && record.message && typeof record.message === 'object') {
209
+ const role = normalizeRole(record.message.role);
210
+ const text = extractMessageText(record.message.content);
211
+ if (!role || !text) {
212
+ return null;
213
+ }
214
+ return { role, text };
215
+ }
216
+ return null;
217
+ }
207
218
 
208
219
  const role = normalizeRole(record.type);
209
220
  if (!role) {
@@ -271,6 +282,14 @@ function applySessionDetailRecordMetadata(record, source, state) {
271
282
  return;
272
283
  }
273
284
 
285
+ if (source === 'pi') {
286
+ if (record.type === 'session') {
287
+ state.sessionId = record.id || state.sessionId;
288
+ state.cwd = record.cwd || state.cwd;
289
+ }
290
+ return;
291
+ }
292
+
274
293
  if (!state.sessionId && record.sessionId) {
275
294
  state.sessionId = record.sessionId;
276
295
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codexmate",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Codex/Claude Code/OpenClaw 配置、会话与任务编排 CLI + Web 工具",
5
5
  "main": "cli.js",
6
6
  "bin": {
package/web-ui/app.js CHANGED
@@ -396,8 +396,8 @@ document.addEventListener('DOMContentLoaded', () => {
396
396
  providerCacheError: '',
397
397
  providerCacheRequestSeq: 0,
398
398
  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 },
401
401
  sessionTrashEnabled: true,
402
402
  sessionTrashItems: [],
403
403
  sessionTrashVisibleCount: SESSION_TRASH_PAGE_SIZE,
@@ -29,14 +29,14 @@ function shouldUseFastSessionBrowseLimit(options = {}) {
29
29
 
30
30
  export function isSessionQueryEnabled(source) {
31
31
  const normalized = normalizeSessionSource(source, '');
32
- return normalized === 'codex' || normalized === 'claude' || normalized === 'gemini' || normalized === 'codebuddy' || normalized === 'all';
32
+ return normalized === 'codex' || normalized === 'claude' || normalized === 'gemini' || normalized === 'codebuddy' || normalized === 'pi' || normalized === 'all';
33
33
  }
34
34
 
35
35
  export function normalizeSessionSource(source, fallback = 'all') {
36
36
  const normalized = typeof source === 'string'
37
37
  ? source.trim().toLowerCase()
38
38
  : '';
39
- if (normalized === 'codex' || normalized === 'claude' || normalized === 'gemini' || normalized === 'codebuddy' || normalized === 'all') {
39
+ if (normalized === 'codex' || normalized === 'claude' || normalized === 'gemini' || normalized === 'codebuddy' || normalized === 'pi' || normalized === 'all') {
40
40
  return normalized;
41
41
  }
42
42
  return fallback;
@@ -209,7 +209,7 @@ export function buildUsageHeatmap(sessions = [], options = {}) {
209
209
  for (const session of list) {
210
210
  if (!session || typeof session !== 'object') continue;
211
211
  const source = normalizeSessionSource(session.source, '');
212
- if (source !== 'codex' && source !== 'claude') continue;
212
+ if (source !== 'codex' && source !== 'claude' && source !== 'pi') continue;
213
213
  const updatedAtMs = Date.parse(session.updatedAt || '');
214
214
  if (!Number.isFinite(updatedAtMs)) continue;
215
215
  normalized.push({
@@ -309,7 +309,7 @@ export function buildUsageHourlyHeatmap(sessions = [], options = {}) {
309
309
  for (const session of list) {
310
310
  if (!session || typeof session !== 'object') continue;
311
311
  const source = normalizeSessionSource(session.source, '');
312
- if (source !== 'codex' && source !== 'claude') continue;
312
+ if (source !== 'codex' && source !== 'claude' && source !== 'pi') continue;
313
313
  const updatedAtMs = Date.parse(session.updatedAt || '');
314
314
  if (!Number.isFinite(updatedAtMs)) continue;
315
315
  const dayStart = toUtcDayStartMs(updatedAtMs);
@@ -402,7 +402,7 @@ export function buildUsageChartGroups(sessions = [], options = {}) {
402
402
  for (const [sessionIndex, session] of list.entries()) {
403
403
  if (!session || typeof session !== 'object') continue;
404
404
  const source = normalizeSessionSource(session.source, '');
405
- if (source !== 'codex' && source !== 'claude') continue;
405
+ if (source !== 'codex' && source !== 'claude' && source !== 'pi') continue;
406
406
  const updatedAtMs = Date.parse(session.updatedAt || '');
407
407
  if (!Number.isFinite(updatedAtMs)) continue;
408
408
  const createdAtMs = Date.parse(session.createdAt || '');
@@ -174,10 +174,6 @@ export function createDashboardComputed() {
174
174
  };
175
175
  });
176
176
  },
177
- currentInstalledCommandCards() {
178
- const targets = Array.isArray(this.installTargetCards) ? this.installTargetCards : [];
179
- return targets.filter((target) => target && target.installed === true);
180
- },
181
177
  installRegistryPreview() {
182
178
  return this.resolveInstallRegistryUrl(this.installRegistryPreset, this.installRegistryCustom);
183
179
  },
@@ -307,7 +307,8 @@ export function createSessionComputed() {
307
307
  { value: "codex", label: this.t("sessions.source.codex") },
308
308
  { value: "claude", label: this.t("sessions.source.claudeCode") },
309
309
  { value: "gemini", label: this.t("sessions.source.gemini") },
310
- { value: "codebuddy", label: this.t("sessions.source.codebuddy") }
310
+ { value: "codebuddy", label: this.t("sessions.source.codebuddy") },
311
+ { value: "pi", label: this.t("sessions.source.pi") }
311
312
  ];
312
313
  },
313
314
  activeSessionExportKey() {
@@ -28,8 +28,8 @@ export function createSessionActionMethods(options = {}) {
28
28
  let error = '';
29
29
  if (!source) {
30
30
  error = '缺少 source 参数';
31
- } else if (source !== 'codex' && source !== 'claude') {
32
- error = 'source 仅支持 codex 或 claude';
31
+ } else if (source !== 'codex' && source !== 'claude' && source !== 'gemini' && source !== 'codebuddy' && source !== 'pi') {
32
+ error = 'source 仅支持 codex、claude、gemini、codebuddy 或 pi';
33
33
  }
34
34
  if (!sessionId && !filePath) {
35
35
  error = error ? `${error},还缺少 sessionId 或 filePath` : '缺少 sessionId 或 filePath 参数';
@@ -67,7 +67,15 @@ export function createSessionActionMethods(options = {}) {
67
67
  return;
68
68
  }
69
69
 
70
- const sourceLabel = context.params.source === 'codex' ? 'Codex' : 'Claude Code';
70
+ const sourceLabel = context.params.source === 'codex'
71
+ ? 'Codex'
72
+ : (context.params.source === 'claude'
73
+ ? 'Claude Code'
74
+ : (context.params.source === 'gemini'
75
+ ? 'Gemini CLI'
76
+ : (context.params.source === 'pi'
77
+ ? 'Pi'
78
+ : (context.params.source === 'codebuddy' ? 'CodeBuddy Code' : context.params.source))));
71
79
  this.activeSession = {
72
80
  source: context.params.source,
73
81
  sourceLabel,
@@ -96,7 +104,7 @@ export function createSessionActionMethods(options = {}) {
96
104
  buildSessionStandaloneUrl(session) {
97
105
  if (!session) return '';
98
106
  const source = typeof session.source === 'string' ? session.source.trim().toLowerCase() : '';
99
- if (!source || (source !== 'codex' && source !== 'claude')) return '';
107
+ if (!source || (source !== 'codex' && source !== 'claude' && source !== 'gemini' && source !== 'codebuddy' && source !== 'pi')) return '';
100
108
  const sessionId = typeof session.sessionId === 'string' ? session.sessionId.trim() : '';
101
109
  const filePath = typeof session.filePath === 'string' ? session.filePath.trim() : '';
102
110
  if (!sessionId && !filePath) return '';
@@ -120,7 +120,7 @@ export function createSessionBrowserMethods(options = {}) {
120
120
  syncSessionPathOptionsForSource(source, nextOptions, mergeWithExisting = false) {
121
121
  const targetSource = source === 'claude'
122
122
  ? 'claude'
123
- : (source === 'gemini' ? 'gemini' : (source === 'all' ? 'all' : 'codex'));
123
+ : (source === 'gemini' ? 'gemini' : (source === 'pi' ? 'pi' : (source === 'all' ? 'all' : 'codex')));
124
124
  const current = Array.isArray(this.sessionPathOptionsMap[targetSource])
125
125
  ? this.sessionPathOptionsMap[targetSource]
126
126
  : [];
@@ -137,7 +137,7 @@ export function createSessionBrowserMethods(options = {}) {
137
137
  refreshSessionPathOptions(source) {
138
138
  const targetSource = source === 'claude'
139
139
  ? 'claude'
140
- : (source === 'gemini' ? 'gemini' : (source === 'all' ? 'all' : 'codex'));
140
+ : (source === 'gemini' ? 'gemini' : (source === 'pi' ? 'pi' : (source === 'all' ? 'all' : 'codex')));
141
141
  const base = Array.isArray(this.sessionPathOptionsMap[targetSource])
142
142
  ? [...this.sessionPathOptionsMap[targetSource]]
143
143
  : [];
@@ -161,7 +161,7 @@ export function createSessionBrowserMethods(options = {}) {
161
161
  async loadSessionPathOptions(options = {}) {
162
162
  const source = options.source === 'claude'
163
163
  ? 'claude'
164
- : (options.source === 'gemini' ? 'gemini' : (options.source === 'all' ? 'all' : 'codex'));
164
+ : (options.source === 'gemini' ? 'gemini' : (options.source === 'pi' ? 'pi' : (options.source === 'all' ? 'all' : 'codex')));
165
165
  const forceRefresh = !!options.forceRefresh;
166
166
  const loaded = !!this.sessionPathOptionsLoadedMap[source];
167
167
  if (!forceRefresh && loaded) {
@@ -507,7 +507,11 @@ export function createSessionBrowserMethods(options = {}) {
507
507
  ? this.t('sessions.source.claudeCode')
508
508
  : (this.sessionFilterSource === 'gemini'
509
509
  ? this.t('sessions.source.gemini')
510
- : (this.sessionFilterSource === 'codebuddy' ? this.t('sessions.source.codebuddy') : this.sessionFilterSource)));
510
+ : (this.sessionFilterSource === 'codebuddy'
511
+ ? this.t('sessions.source.codebuddy')
512
+ : (this.sessionFilterSource === 'pi'
513
+ ? this.t('sessions.source.pi')
514
+ : this.sessionFilterSource))));
511
515
  chips.push({ key: 'source', title: this.t('sessions.filters.source'), value: label });
512
516
  }
513
517
  if (this.sessionPathFilter) {
@@ -10,7 +10,7 @@ export function createSessionTrashMethods(options = {}) {
10
10
  const deletedAt = typeof result.deletedAt === 'string' && result.deletedAt
11
11
  ? result.deletedAt
12
12
  : new Date().toISOString();
13
- const source = session && (session.source === 'claude' || session.source === 'gemini')
13
+ const source = session && (session.source === 'claude' || session.source === 'gemini' || session.source === 'codebuddy' || session.source === 'pi')
14
14
  ? session.source
15
15
  : 'codex';
16
16
  return {
@@ -18,7 +18,7 @@ export function createSessionTrashMethods(options = {}) {
18
18
  source,
19
19
  sourceLabel: session && typeof session.sourceLabel === 'string' && session.sourceLabel
20
20
  ? session.sourceLabel
21
- : (source === 'claude' ? 'Claude Code' : (source === 'gemini' ? 'Gemini CLI' : 'Codex')),
21
+ : (source === 'claude' ? 'Claude Code' : (source === 'gemini' ? 'Gemini CLI' : (source === 'pi' ? 'Pi' : (source === 'codebuddy' ? 'CodeBuddy Code' : 'Codex')))),
22
22
  sessionId: session && typeof session.sessionId === 'string' ? session.sessionId : '',
23
23
  title: session && typeof session.title === 'string' && session.title
24
24
  ? session.title
@@ -144,7 +144,8 @@ export function createStartupClaudeMethods(options = {}) {
144
144
  codex: statusRes.toolConfigPermissions.codex === true,
145
145
  claude: statusRes.toolConfigPermissions.claude === true,
146
146
  opencode: statusRes.toolConfigPermissions.opencode === true,
147
- kilocode: statusRes.toolConfigPermissions.kilocode === true
147
+ kilocode: statusRes.toolConfigPermissions.kilocode === true,
148
+ openclaw: statusRes.toolConfigPermissions.openclaw === true
148
149
  };
149
150
  }
150
151
  this.providersList = listRes.providers;
@@ -3,7 +3,7 @@ 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 === 'opencode' || target === 'kilocode' ? target : '';
6
+ return target === 'codex' || target === 'claude' || target === 'opencode' || target === 'kilocode' || target === 'openclaw' ? target : '';
7
7
  }
8
8
 
9
9
  function normalizePermissions(value) {
@@ -12,7 +12,8 @@ export function createToolConfigPermissionMethods(options = {}) {
12
12
  codex: source.codex === true,
13
13
  claude: source.claude === true,
14
14
  opencode: source.opencode === true,
15
- kilocode: source.kilocode === true
15
+ kilocode: source.kilocode === true,
16
+ openclaw: source.openclaw === true
16
17
  };
17
18
  }
18
19
 
@@ -439,7 +439,8 @@ export function createWebUiPreferencesMethods(options = {}) {
439
439
  codex: source.toolConfigPermissions.codex === true,
440
440
  claude: source.toolConfigPermissions.claude === true,
441
441
  opencode: source.toolConfigPermissions.opencode === true,
442
- kilocode: source.toolConfigPermissions.kilocode === true
442
+ kilocode: source.toolConfigPermissions.kilocode === true,
443
+ openclaw: source.toolConfigPermissions.openclaw === true
443
444
  };
444
445
  }
445
446
  if (Array.isArray(source.deletedClaudeSettingsImports)) {
@@ -619,14 +619,8 @@ const en = Object.freeze({
619
619
 
620
620
  // Docs panel
621
621
  'docs.title': 'CLI Install',
622
- 'docs.subtitle': 'Install commands for Claude Code / Gemini CLI / CodeBuddy Code / Codex CLI / KiloCode CLI.',
623
622
  'docs.section.commands': 'Commands',
624
- 'docs.section.commandsNote': 'Copy and run directly.',
625
- 'docs.installed.title': 'Currently installed commands',
626
- 'docs.installed.count': '{count} installed',
627
- 'docs.installed.empty': 'No installed commands detected',
628
623
  'docs.section.faq': 'FAQ',
629
- 'docs.section.faqNote': 'Common issues and tips.',
630
624
  'docs.command.aria': '{name} command',
631
625
  'docs.registryHintPrefix': 'Command will append:',
632
626
  'docs.registryHintCustom': 'Enter a full URL (http/https) to append as registry.',
@@ -634,8 +628,6 @@ const en = Object.freeze({
634
628
  'docs.meta.bin': 'bin: {bin}',
635
629
  'docs.termuxLabel': 'Termux',
636
630
  'docs.termuxAria': 'Termux Codex CLI command',
637
- 'docs.rule.1': 'Commands depend on package manager, registry and action.',
638
- 'docs.rule.2': 'Custom registry is used for install/update only.',
639
631
  'docs.tip.win.1': 'If PowerShell reports permission errors (EACCES/EPERM), run the install command as Administrator.',
640
632
  'docs.tip.win.2': 'If the command is still not found after install, reopen the terminal and run: where codex / where claude / where gemini / where codebuddy.',
641
633
  'docs.tip.win.3': 'If your network blocks npm, try switching registry presets (npmmirror / Tencent / Custom).',
@@ -653,6 +645,7 @@ const en = Object.freeze({
653
645
  'sessions.source.claudeCode': 'Claude Code',
654
646
  'sessions.source.gemini': 'Gemini CLI',
655
647
  'sessions.source.codebuddy': 'CodeBuddy Code',
648
+ 'sessions.source.pi': 'Pi',
656
649
  'sessions.loadingList': 'Loading sessions...',
657
650
  'sessions.empty': 'No sessions found',
658
651
  'sessions.unknownTime': 'unknown time',
@@ -1544,6 +1537,11 @@ const en = Object.freeze({
1544
1537
  'toolConfig.kilocode.lockedTitle': 'Read-only mode',
1545
1538
  'toolConfig.kilocode.lockedDesc': 'Enable write access to auto-sync config or launch KiloCode.',
1546
1539
  '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.',
1540
+ 'toolConfig.openclaw.title': 'OpenClaw config writes',
1541
+ 'toolConfig.openclaw.desc': 'Read-only by default; enable to apply OpenClaw configs and write AGENTS.md and other workspace files.',
1542
+ 'toolConfig.openclaw.confirmMessage': 'When enabled, actions in the OpenClaw tab can write openclaw.json and OpenClaw workspace files (AGENTS.md, SOUL.md, etc.).',
1543
+ 'toolConfig.openclaw.lockedTitle': 'OpenClaw is read-only',
1544
+ 'toolConfig.openclaw.lockedDesc': 'OpenClaw config is not written here. To apply configs or edit workspace files, enable write access on this tab first.',
1547
1545
  'kilocode.providerModel.title': 'KiloCode provider / model',
1548
1546
  'kilocode.connection.title': 'KiloCode connection',
1549
1547
  'kilocode.targetFile': 'Active KiloCode config: {path} · {status}',
@@ -621,14 +621,8 @@ const ja = Object.freeze({
621
621
 
622
622
  // Docs panel
623
623
  'docs.title': 'CLI インストールドキュメント',
624
- 'docs.subtitle': 'Claude Code / Gemini CLI / CodeBuddy Code / Codex CLI / KiloCode CLI コマンドを表示。',
625
624
  'docs.section.commands': 'インストールコマンド',
626
- 'docs.section.commandsNote': 'コマンドは直接コピーできます。',
627
- 'docs.installed.title': '現在インストール済みのコマンド',
628
- 'docs.installed.count': '{count} 個インストール済み',
629
- 'docs.installed.empty': 'インストール済みコマンドは検出されていません',
630
625
  'docs.section.faq': 'よくある質問',
631
- 'docs.section.faqNote': 'よくある質問は以下をご覧ください。',
632
626
  'docs.command.aria': '{name} コマンド',
633
627
  'docs.registryHintPrefix': 'コマンドに付加:',
634
628
  'docs.registryHintCustom': '完全な URL(http/https 含む)を入力してください。コマンドに自動付加されます。',
@@ -636,8 +630,6 @@ const ja = Object.freeze({
636
630
  'docs.meta.bin': 'bin: {bin}',
637
631
  'docs.termuxLabel': 'Termux',
638
632
  'docs.termuxAria': 'Termux Codex CLI command',
639
- 'docs.rule.1': 'コマンドは現在のパッケージマネージャー、ミラー、操作に基づいて生成されます。',
640
- 'docs.rule.2': 'カスタムミラーはインストールとアップグレードにのみ使用されます。',
641
633
  'docs.tip.win.1': 'PowerShell で権限不足(EACCES/EPERM)エラーが発生した場合は、管理者としてインストールコマンドを実行してください。',
642
634
  'docs.tip.win.2': 'インストール後にコマンドが見つからない場合は、ターミナルを再起動して実行:where codex / where claude / where gemini / where codebuddy。',
643
635
  'docs.tip.win.3': '企業ネットワークで制限がある場合は、ミラーソース(npmmirror / Tencent Cloud / カスタム)を切り替えてください。',
@@ -655,6 +647,7 @@ const ja = Object.freeze({
655
647
  'sessions.source.claudeCode': 'Claude Code',
656
648
  'sessions.source.gemini': 'Gemini',
657
649
  'sessions.source.codebuddy': 'CodeBuddy',
650
+ 'sessions.source.pi': 'Pi',
658
651
  'sessions.loadingList': 'セッション一覧を読み込み中...',
659
652
  'sessions.empty': 'セッションがありません',
660
653
  'sessions.unknownTime': '不明な時間',
@@ -1531,6 +1524,11 @@ const ja = Object.freeze({
1531
1524
  'toolConfig.kilocode.lockedTitle': '読み取り専用モード',
1532
1525
  'toolConfig.kilocode.lockedDesc': '書き込み権限を有効化すると、設定の自動同期または KiloCode の起動ができます。',
1533
1526
  'toolConfig.kilocode.confirmMessage': '有効化すると、KiloCode タブ内の操作が ~/.config/kilo/kilo.jsonc または既存の kilo.json に書き込み、ローカル KiloCode を起動できます。',
1527
+ 'toolConfig.openclaw.title': 'OpenClaw 設定の書き込み',
1528
+ 'toolConfig.openclaw.desc': '既定は読み取り専用。有効化すると OpenClaw 設定を適用し、AGENTS.md などのワークスペースファイルを書き込みできます。',
1529
+ 'toolConfig.openclaw.confirmMessage': '有効化すると、OpenClaw タブ内の操作が openclaw.json およびワークスペースファイル(AGENTS.md、SOUL.md など)に書き込みできます。',
1530
+ 'toolConfig.openclaw.lockedTitle': 'OpenClaw は読み取り専用です',
1531
+ 'toolConfig.openclaw.lockedDesc': 'ここでは OpenClaw 設定を書き込みません。設定を適用またはワークスペースファイルを編集するには、このタブの書き込みスイッチを有効化してください。',
1534
1532
  'kilocode.providerModel.title': 'KiloCode プロバイダー / モデル',
1535
1533
  'kilocode.connection.title': 'KiloCode 接続',
1536
1534
  'kilocode.targetFile': '有効な KiloCode 設定:{path} · {status}',
@@ -826,14 +826,8 @@ const vi = Object.freeze({
826
826
  'modal.openclaw.quick.optionsHint': 'Khi tắt ghi đè, chỉ điền các trường còn thiếu.',
827
827
  'modal.openclaw.quick.writeToEditor': 'Ghi vào editor',
828
828
  'docs.title': 'Cài CLI',
829
- 'docs.subtitle': 'Lệnh cài đặt cho Claude Code / Gemini CLI / CodeBuddy Code / Codex CLI / KiloCode CLI.',
830
829
  'docs.section.commands': 'Lệnh',
831
- 'docs.section.commandsNote': 'Sao chép và chạy trực tiếp.',
832
- 'docs.installed.title': 'Lệnh hiện đã cài đặt',
833
- 'docs.installed.count': 'Đã cài {count}',
834
- 'docs.installed.empty': 'Chưa phát hiện lệnh đã cài',
835
830
  'docs.section.faq': 'FAQ',
836
- 'docs.section.faqNote': 'Vấn đề phổ biến và mẹo.',
837
831
  'docs.command.aria': 'Lệnh {name}',
838
832
  'docs.registryHintPrefix': 'Lệnh sẽ thêm:',
839
833
  'docs.registryHintCustom': 'Nhập URL đầy đủ (http/https) để thêm làm registry.',
@@ -841,8 +835,6 @@ const vi = Object.freeze({
841
835
  'docs.meta.bin': 'bin: {bin}',
842
836
  'docs.termuxLabel': 'Termux',
843
837
  'docs.termuxAria': 'Lệnh Codex CLI cho Termux',
844
- 'docs.rule.1': 'Lệnh phụ thuộc vào trình quản lý gói, registry và hành động.',
845
- 'docs.rule.2': 'Registry tùy chỉnh chỉ dùng cho cài đặt/cập nhật.',
846
838
  'docs.tip.win.1': 'Nếu PowerShell báo lỗi quyền (EACCES/EPERM), chạy lệnh cài đặt với quyền Administrator.',
847
839
  'docs.tip.win.2': 'Nếu lệnh không tìm thấy sau cài đặt, mở lại terminal và chạy: where codex / where claude.',
848
840
  'docs.tip.win.3': 'Nếu mạng chặn npm, thử chuyển registry (npmmirror / Tencent / Tùy chỉnh).',
@@ -858,6 +850,7 @@ const vi = Object.freeze({
858
850
  'sessions.source.claudeCode': 'Claude Code',
859
851
  'sessions.source.gemini': 'Gemini CLI',
860
852
  'sessions.source.codebuddy': 'CodeBuddy Code',
853
+ 'sessions.source.pi': 'Pi',
861
854
  'sessions.loadingList': 'Đang tải phiên làm việc...',
862
855
  'sessions.empty': 'Không tìm thấy phiên nào',
863
856
  'sessions.unknownTime': 'thời gian không rõ',
@@ -1516,6 +1509,11 @@ const vi = Object.freeze({
1516
1509
  'toolConfig.kilocode.lockedTitle': 'Chế độ chỉ đọc',
1517
1510
  'toolConfig.kilocode.lockedDesc': 'Bật quyền ghi để tự động đồng bộ cấu hình hoặc khởi chạy KiloCode.',
1518
1511
  '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ộ.',
1512
+ 'toolConfig.openclaw.title': 'Ghi cấu hình OpenClaw',
1513
+ '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.',
1514
+ '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.).',
1515
+ 'toolConfig.openclaw.lockedTitle': 'OpenClaw đang ở chế độ chỉ đọc',
1516
+ 'toolConfig.openclaw.lockedDesc': 'Không ghi cấu hình OpenClaw ở đây. Để áp dụng cấu hình hoặc sửa tệp workspace, hãy bật quyền ghi trên tab này trước.',
1519
1517
  'kilocode.providerModel.title': 'Nhà cung cấp / mô hình KiloCode',
1520
1518
  'kilocode.connection.title': 'Kết nối KiloCode',
1521
1519
  'kilocode.targetFile': 'Cấu hình KiloCode đang dùng: {path} · {status}',
@@ -619,14 +619,8 @@ const zhTw = Object.freeze({
619
619
 
620
620
  // Docs panel
621
621
  'docs.title': 'CLI 安裝文檔',
622
- 'docs.subtitle': '查看 Claude Code / Gemini CLI / CodeBuddy Code / Codex CLI / KiloCode CLI 命令。',
623
622
  'docs.section.commands': '安裝命令',
624
- 'docs.section.commandsNote': '命令可直接複製。',
625
- 'docs.installed.title': '目前已安裝命令',
626
- 'docs.installed.count': '已安裝 {count} 個',
627
- 'docs.installed.empty': '未偵測到已安裝命令',
628
623
  'docs.section.faq': '常見問題',
629
- 'docs.section.faqNote': '常見問題見下。',
630
624
  'docs.command.aria': '{name} 命令',
631
625
  'docs.registryHintPrefix': '命令將附加:',
632
626
  'docs.registryHintCustom': '請輸入完整 URL(含 http/https),將自動附加到命令。',
@@ -634,8 +628,6 @@ const zhTw = Object.freeze({
634
628
  'docs.meta.bin': 'bin: {bin}',
635
629
  'docs.termuxLabel': 'Termux',
636
630
  'docs.termuxAria': 'Termux Codex CLI command',
637
- 'docs.rule.1': '命令按目前包管理器、鏡像與操作生成。',
638
- 'docs.rule.2': '自訂鏡像僅用於安裝與升級。',
639
631
  'docs.tip.win.1': 'PowerShell 報權限不足(EACCES/EPERM)時,請以管理員身份執行安裝命令。',
640
632
  'docs.tip.win.2': '安裝後若仍提示找不到命令,重開終端並執行:where codex / where claude / where gemini / where codebuddy。',
641
633
  'docs.tip.win.3': '公司網路受限時,可先切換鏡像源快捷項(npmmirror / 騰訊雲 / 自訂)。',
@@ -653,6 +645,7 @@ const zhTw = Object.freeze({
653
645
  'sessions.source.claudeCode': 'Claude Code',
654
646
  'sessions.source.gemini': 'Gemini CLI',
655
647
  'sessions.source.codebuddy': 'CodeBuddy Code',
648
+ 'sessions.source.pi': 'Pi',
656
649
  'sessions.loadingList': '會話載入中...',
657
650
  'sessions.empty': '暫無可用會話記錄',
658
651
  'sessions.unknownTime': '未知時間',
@@ -1544,6 +1537,11 @@ const zhTw = Object.freeze({
1544
1537
  'toolConfig.kilocode.lockedTitle': '唯讀模式',
1545
1538
  'toolConfig.kilocode.lockedDesc': '啟用寫入權限後可自動同步設定或啟動 KiloCode。',
1546
1539
  'toolConfig.kilocode.confirmMessage': '開啟後,KiloCode tab 內的操作會寫入 ~/.config/kilo/kilo.jsonc 或既有的 kilo.json,並可啟動本機 KiloCode。',
1540
+ 'toolConfig.openclaw.title': 'OpenClaw 設定寫入',
1541
+ 'toolConfig.openclaw.desc': '預設唯讀;啟用後可套用 OpenClaw 設定並寫入 AGENTS.md 與其他工作區檔案。',
1542
+ 'toolConfig.openclaw.confirmMessage': '開啟後,OpenClaw tab 內的操作會寫入 openclaw.json 及 OpenClaw 工作區檔案(AGENTS.md、SOUL.md 等)。',
1543
+ 'toolConfig.openclaw.lockedTitle': 'OpenClaw 目前為唯讀',
1544
+ 'toolConfig.openclaw.lockedDesc': '這裡不會寫入 OpenClaw 設定。要套用設定或編輯工作區檔案,請先開啟本 tab 的寫入開關。',
1547
1545
  'kilocode.providerModel.title': 'KiloCode provider / 模型',
1548
1546
  'kilocode.connection.title': 'KiloCode 連線',
1549
1547
  'kilocode.targetFile': 'KiloCode 生效設定:{path} · {status}',
@@ -619,14 +619,8 @@ const zh = Object.freeze({
619
619
 
620
620
  // Docs panel
621
621
  'docs.title': 'CLI 安装文档',
622
- 'docs.subtitle': '查看 Claude Code / Gemini CLI / CodeBuddy Code / Codex CLI / KiloCode CLI 命令。',
623
622
  'docs.section.commands': '安装命令',
624
- 'docs.section.commandsNote': '命令可直接复制。',
625
- 'docs.installed.title': '当前已安装命令',
626
- 'docs.installed.count': '已安装 {count} 个',
627
- 'docs.installed.empty': '未检测到已安装命令',
628
623
  'docs.section.faq': '常见问题',
629
- 'docs.section.faqNote': '常见问题见下。',
630
624
  'docs.command.aria': '{name} 命令',
631
625
  'docs.registryHintPrefix': '命令将附加:',
632
626
  'docs.registryHintCustom': '请输入完整 URL(含 http/https),将自动附加到命令。',
@@ -634,8 +628,6 @@ const zh = Object.freeze({
634
628
  'docs.meta.bin': 'bin: {bin}',
635
629
  'docs.termuxLabel': 'Termux',
636
630
  'docs.termuxAria': 'Termux Codex CLI command',
637
- 'docs.rule.1': '命令按当前包管理器、镜像与操作生成。',
638
- 'docs.rule.2': '自定义镜像仅用于安装与升级。',
639
631
  'docs.tip.win.1': 'PowerShell 报权限不足(EACCES/EPERM)时,请以管理员身份执行安装命令。',
640
632
  'docs.tip.win.2': '安装后若仍提示找不到命令,重开终端并执行:where codex / where claude / where gemini / where codebuddy。',
641
633
  'docs.tip.win.3': '公司网络受限时,可先切换镜像源快捷项(npmmirror / 腾讯云 / 自定义)。',
@@ -653,6 +645,7 @@ const zh = Object.freeze({
653
645
  'sessions.source.claudeCode': 'Claude Code',
654
646
  'sessions.source.gemini': 'Gemini CLI',
655
647
  'sessions.source.codebuddy': 'CodeBuddy Code',
648
+ 'sessions.source.pi': 'Pi',
656
649
  'sessions.loadingList': '会话加载中...',
657
650
  'sessions.empty': '暂无可用会话记录',
658
651
  'sessions.unknownTime': '未知时间',
@@ -1544,6 +1537,11 @@ const zh = Object.freeze({
1544
1537
  'toolConfig.kilocode.lockedTitle': '只读模式',
1545
1538
  'toolConfig.kilocode.lockedDesc': '开启写入权限后可保存配置或启动 KiloCode。',
1546
1539
  'toolConfig.kilocode.confirmMessage': '打开后,KiloCode tab 内的操作会写入 ~/.config/kilo/kilo.jsonc 或已存在的 kilo.json,并可启动本机 KiloCode。',
1540
+ 'toolConfig.openclaw.title': 'OpenClaw 配置写入',
1541
+ 'toolConfig.openclaw.desc': '默认只读;开启后可应用 OpenClaw 配置、写入 AGENTS.md 与其他工作区文件。',
1542
+ 'toolConfig.openclaw.confirmMessage': '打开后,OpenClaw tab 内的操作会写入 openclaw.json 及 OpenClaw 工作区文件(AGENTS.md、SOUL.md 等)。',
1543
+ 'toolConfig.openclaw.lockedTitle': 'OpenClaw 当前为只读',
1544
+ 'toolConfig.openclaw.lockedDesc': '这里不会写入 OpenClaw 配置。要应用配置或编辑工作区文件,请先打开本 tab 的写入开关。',
1547
1545
  'kilocode.providerModel.title': 'KiloCode provider / model',
1548
1546
  'kilocode.connection.title': 'KiloCode 连接',
1549
1547
  'kilocode.targetFile': 'KiloCode 生效配置:{path} · {status}',
@@ -11,6 +11,23 @@
11
11
  <button type="button" :class="['segment', { active: configMode === 'openclaw' }]" @click="switchConfigMode('openclaw')">{{ t('tab.config.openclaw') }}</button>
12
12
  </div>
13
13
  <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
+
14
31
  <section class="settings-card settings-card--wide openclaw-summary-card" aria-label="OpenClaw 当前摘要">
15
32
  <div class="settings-card-body">
16
33
  <div class="openclaw-summary-header">
@@ -32,10 +49,12 @@
32
49
  </div>
33
50
  </section>
34
51
 
52
+ <div class="tool-config-write-scope" :class="{ locked: !isToolConfigWriteAllowed('openclaw') }">
53
+ <div class="tool-config-write-body">
35
54
  <section class="settings-card settings-card--wide openclaw-workspace-card">
36
55
  <div class="settings-card-body">
37
56
  <div class="openclaw-tools-grid">
38
- <button class="openclaw-tool-btn" @click="openOpenclawAgentsEditor" :disabled="loading || !!initError || agentsLoading">
57
+ <button class="openclaw-tool-btn" @click="openOpenclawAgentsEditor" :disabled="loading || !!initError || agentsLoading || !isToolConfigWriteAllowed('openclaw')">
39
58
  <div class="tool-icon">📄</div>
40
59
  <div class="tool-content">
41
60
  <div class="tool-title">AGENTS.md</div>
@@ -52,7 +71,7 @@
52
71
  type="button"
53
72
  class="openclaw-tool-btn"
54
73
  @click="openOpenclawQuickWorkspaceFile(fileName)"
55
- :disabled="loading || !!initError || agentsLoading">
74
+ :disabled="loading || !!initError || agentsLoading || !isToolConfigWriteAllowed('openclaw')">
56
75
  <div class="tool-icon">📄</div>
57
76
  <div class="tool-content">
58
77
  <div class="tool-title">{{ fileName }}</div>
@@ -70,13 +89,14 @@
70
89
  <div class="settings-card-body">
71
90
  <div class="card-list openclaw-card-list">
72
91
  <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"
92
+ :class="['card', { active: currentOpenclawConfig === name, disabled: !isToolConfigWriteAllowed('openclaw') }]"
93
+ @click="isToolConfigWriteAllowed('openclaw') && applyOpenclawConfig(name)"
94
+ @keydown.enter.self.prevent="isToolConfigWriteAllowed('openclaw') && applyOpenclawConfig(name)"
95
+ @keydown.space.self.prevent="isToolConfigWriteAllowed('openclaw') && applyOpenclawConfig(name)"
96
+ :tabindex="isToolConfigWriteAllowed('openclaw') ? 0 : -1"
78
97
  role="button"
79
98
  :aria-label="t('openclaw.action.applyAria', { name })"
99
+ :aria-disabled="!isToolConfigWriteAllowed('openclaw') ? 'true' : null"
80
100
  :aria-current="currentOpenclawConfig === name ? 'true' : null">
81
101
  <div class="card-leading">
82
102
  <div class="card-icon">{{ name.charAt(0).toUpperCase() }}</div>
@@ -95,7 +115,7 @@
95
115
  {{ openclawHasContent(config) ? t('openclaw.configured') : t('openclaw.notConfigured') }}
96
116
  </span>
97
117
  <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')">
118
+ <button class="card-action-btn" @click="openOpenclawEditModal(name)" :disabled="!isToolConfigWriteAllowed('openclaw')" :aria-label="t('openclaw.action.editAria', { name })" :title="t('openclaw.action.edit')">
99
119
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
100
120
  <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
101
121
  <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 +133,14 @@
113
133
  </div>
114
134
  </div>
115
135
  </section>
136
+ <div v-if="!isToolConfigWriteAllowed('openclaw')" class="tool-config-write-overlay">
137
+ <div class="tool-config-write-overlay-card">
138
+ <div class="tool-config-write-overlay-title">{{ t('toolConfig.openclaw.lockedTitle') }}</div>
139
+ <p>{{ t('toolConfig.openclaw.lockedDesc') }}</p>
140
+ <button type="button" class="btn-tool" @click="setToolConfigPermission('openclaw', true)" :disabled="toolConfigPermissionSaving.openclaw">{{ t('toolConfig.enableWrite') }}</button>
141
+ </div>
142
+ </div>
143
+ </div>
144
+ </div>
116
145
  </div>
117
146
  </div>