Repository navigation
feat(web): 种子页按当前槽 settings.json 显示真实配置 #608
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
b164d89
feat(web): 种子页按当前槽 settings.json 显示
dofastted b899caa
fix(web): 空的 claude.setting 不再显示成默认配置
dofastted c7ceaab
fix(panel): 缺失的 claude.setting 返回 null
dofastted 303f82a
fix(panel): 遥测 sidecar 刷新失败时仍返回 200 保存播种
cursoragent 2af464e
docs(changelog): 未发布条目 — 种子页当前槽配置
cursoragent File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| # Product | ||
|
|
||
| <!-- impeccable:product-schema 1 --> | ||
|
|
||
| ## Platform | ||
|
|
||
| web | ||
|
|
||
| ## Users | ||
|
|
||
| 本机管理员。在深色运维控制台里打开某个虚拟机槽位,核对并改写该槽 Claude Code 的种子配置。 | ||
|
|
||
| ## Product Purpose | ||
|
|
||
| vm2api 把 Claude / Codex 账号做成可调度的虚拟机槽,并对外提供兼容上游的 API。种子页的成功标准:管理员不用进容器,就能看见当前槽 `settings.json` 里实际生效的配置项,改完能播种,并实时看到遥测开或关。 | ||
|
|
||
| ## Positioning | ||
|
|
||
| 种子不是一份预设表单。它读当前槽家目录里已经写好的 Claude 配置,按文件内容逐项显示,再把改动写回同一份文件。 | ||
|
|
||
| ## Operating Context | ||
|
|
||
| 管理员在 `/vm/$id` 的「种子」页签工作。数据来自该槽 `cli-home/.claude/settings.json`、`claude.setting` 和种子策略。遥测进程状态来自槽详情里的实时观测。 | ||
|
|
||
| ## Capabilities and Constraints | ||
|
|
||
| - 显示当前槽 `settings.json` 的真实键值,而不是只显示种子策略里的默认串。 | ||
| - `claude.setting` 以可编辑文本提供,保存时写回并重新播种。 | ||
| - 遥测开、关状态实时刷新;配置开关和进程是否在跑是两件事。 | ||
| - 「拒客户端 settings」和「拒客户端身份」按当前策略准确显示,并可切换。 | ||
| - 平台是现有 React 面板,不另起一套视觉系统。 | ||
|
|
||
| ## Product Principles | ||
|
|
||
| - 槽内文件是事实,表单默认值不是。 | ||
| - 开和关都要能被看见,未知不能伪装成关闭。 | ||
| - 能改的项和只读观测分开。 | ||
| - 播种是显式动作,不在切换开关时偷偷写盘。 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| import test from 'node:test' | ||
| import assert from 'node:assert/strict' | ||
| import fs from 'node:fs' | ||
| import os from 'node:os' | ||
| import path from 'node:path' | ||
| import { createPanelHandler } from '../../src/lib/admin/panel-routes.mjs' | ||
| import { DEFAULT_CLAUDE_SETTING } from '../../src/lib/protocol/seed-policy.mjs' | ||
|
|
||
| function seed(root, id) { | ||
| const dir = path.join(root, 'vms') | ||
| fs.mkdirSync(dir, { recursive: true }) | ||
| fs.writeFileSync( | ||
| path.join(dir, `${id}.json`), | ||
| JSON.stringify({ | ||
| id, | ||
| name: id, | ||
| status: 'stopped', | ||
| seed_policy: { claude_setting: 'FROM_POLICY=1' }, | ||
| }), | ||
| ) | ||
| } | ||
|
|
||
| function handler(project) { | ||
| const response = {} | ||
| const handlePanel = createPanelHandler({ | ||
| cfg: { paths: { project }, vm: { id: null } }, | ||
| requireAuth(req) { | ||
| req.apiKeyKind = 'master' | ||
| req.panelRole = 'admin' | ||
| return true | ||
| }, | ||
| json(_res, status, payload) { | ||
| response.status = status | ||
| response.body = payload | ||
| return true | ||
| }, | ||
| readBody: async () => ({}), | ||
| proxyPool: { unbindVm() {} }, | ||
| }) | ||
| return { handlePanel, response } | ||
| } | ||
|
|
||
| test('GET seed-settings keeps a missing claude.setting null', async () => { | ||
| const root = fs.mkdtempSync(path.join(os.tmpdir(), 'kin-seed-get-')) | ||
| try { | ||
| seed(root, 'vm-01') | ||
| const { handlePanel, response } = handler(root) | ||
| await handlePanel({ method: 'GET' }, {}, new URL('http://localhost/api/panel/vms/vm-01/seed-settings')) | ||
| assert.equal(response.status, 200, JSON.stringify(response.body).slice(0, 400)) | ||
| assert.equal(response.body.data.claude_setting, null) | ||
| assert.equal(response.body.data.settings_json, null) | ||
| assert.equal(response.body.data.seed_policy.claude_setting, 'FROM_POLICY=1') | ||
| } finally { | ||
| fs.rmSync(root, { recursive: true, force: true }) | ||
| } | ||
| }) | ||
|
|
||
| test('GET seed-settings returns an empty claude.setting file as empty', async () => { | ||
| const root = fs.mkdtempSync(path.join(os.tmpdir(), 'kin-seed-empty-')) | ||
| try { | ||
| seed(root, 'vm-01') | ||
| const dir = path.join(root, 'vms', 'vm-01', 'cli-home', '.claude') | ||
| fs.mkdirSync(dir, { recursive: true }) | ||
| fs.writeFileSync(path.join(dir, 'claude.setting'), '\n') | ||
| fs.writeFileSync(path.join(dir, 'settings.json'), JSON.stringify({ env: { TZ: 'UTC' } })) | ||
| const { handlePanel, response } = handler(root) | ||
| await handlePanel({ method: 'GET' }, {}, new URL('http://localhost/api/panel/vms/vm-01/seed-settings')) | ||
| assert.equal(response.status, 200, JSON.stringify(response.body).slice(0, 400)) | ||
| assert.equal(response.body.data.claude_setting, '') | ||
| assert.equal(response.body.data.seed_policy.claude_setting, '') | ||
| assert.equal(response.body.data.settings_json.env.TZ, 'UTC') | ||
| } finally { | ||
| fs.rmSync(root, { recursive: true, force: true }) | ||
| } | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,18 +1,19 @@ | ||
| import { describe, expect, it } from 'vitest' | ||
| import { alignTelemetryDraft } from './seed-policy-card' | ||
| import { seedSaveBody } from './seed-settings' | ||
| import { telemetryEnabled } from './telemetry-policy' | ||
|
|
||
| describe('seed card follows the telemetry bit', () => { | ||
| describe('seed save follows the telemetry bit', () => { | ||
| it('drops contradictory mirrors so the switch stays on', () => { | ||
| const draft = alignTelemetryDraft({ | ||
| const body = seedSaveBody({ | ||
| telemetry_disabled: false, | ||
| disable_nonessential_traffic: false, | ||
| do_not_track: true, | ||
| reject_client_settings: true, | ||
| reject_client_metadata_identity: true, | ||
| claude_setting: 'A=1', | ||
| }) | ||
| expect(telemetryEnabled(draft)).toBe(true) | ||
| expect(draft.disable_nonessential_traffic).toBe(true) | ||
| expect(draft.do_not_track).toBe(false) | ||
| expect(telemetryEnabled(body)).toBe(true) | ||
| expect(body.disable_nonessential_traffic).toBe(true) | ||
| expect(body.do_not_track).toBe(false) | ||
| }) | ||
| }) |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Save toast hides sidecar failure
Medium Severity
PUT /seed-settingsnow stays 200 when telemetry sidecar refresh fails, but seed save still toasts已播种and the settings telemetry pane counts those writes as successes. Nothing reads the responseservicefield, so a failed process restart looks like a complete seed.Additional Locations (1)
web/src/features/vm/detail-page.tsx#L228-L241Reviewed by Cursor Bugbot for commit 2af464e. Configure here.