diff --git a/packages/core/src/internal/session-support.ts b/packages/core/src/internal/session-support.ts index 2b5bbe2..40fdaec 100644 --- a/packages/core/src/internal/session-support.ts +++ b/packages/core/src/internal/session-support.ts @@ -73,7 +73,7 @@ const MAX_TMP_ID_ATTEMPTS = 16; * directory name is the workspace_id, shaped like `tmp-<8hex>`; if it collides with * an existing directory, regenerate the id. No symlinks are created inside the Workspace: * the model composes absolute paths (to Agent State, scratchpad, etc.) directly from the - * Environment placeholders (Project Dir / Agent ID) in the system prompt. + * Environment placeholders (App Data Dir / Agent ID) in the system prompt. */ export async function createTempWorkspace( root: string, diff --git a/packages/core/src/state/agent-state.ts b/packages/core/src/state/agent-state.ts index 42f8fa4..3334698 100644 --- a/packages/core/src/state/agent-state.ts +++ b/packages/core/src/state/agent-state.ts @@ -29,6 +29,7 @@ import { PROVIDER_PLACEHOLDER, MODEL_ID_PLACEHOLDER, DATE_PLACEHOLDER, + agentStateVersion, defaultAgentsMd, defaultSystemConfig, OS_VERSION_PLACEHOLDER, @@ -83,7 +84,7 @@ export interface SessionEnvironmentValues { cwd: string; /** The Agent id this Session belongs to (system Prompt placeholder {{AGENT_ID}}). */ agentId: string; - /** Absolute path to this Project's directory (system Prompt placeholder {{PROJECT_DIR}}; Agent State/scratchpad paths are derived from it). */ + /** Absolute path to this Project's directory — PenguinHarness's app data root, injected via {{PROJECT_DIR}} and shown to the model as the Environment's "App Data Dir" line (Agent State/scratchpad paths live under its `agents/`). */ projectDir: string; /** The session model's provider group (system Prompt placeholder {{PROVIDER}}; paired with modelId to form the model reference). */ provider: string; @@ -212,6 +213,48 @@ export async function provisionProjectAgents(opts?: { return agentIds; } +/** + * Rewrites an Agent's `system_config.yaml` to the current code defaults + * (`defaultSystemConfig()`) — the config-side analogue of updating an installed Skill to + * the current library version. + * + * Exact semantics: only the identity fields of the existing file are preserved — `name`, + * `description` and the Agent State `version` (an invalid or missing version normalizes + * to 1); **everything else is replaced by the defaults**: `system_prompt`, `max_turns`, + * `model.*`, `compaction.*` (including its prompt) and `tools` (the builtin list and + * `mcpServers`). Keys outside the default schema are dropped, and YAML comments are not + * preserved (the file is rewritten from the default object). Other Agent State files + * (AGENTS.md, skills/, vault, memory/ …) are untouched. Returns the config written. + * + * Requires an existing Agent: unlike `loadOrInitAgentState` this never initializes a new + * one — it throws when `system_config.yaml` is missing. + */ +export async function resetSystemConfigToDefaults( + root: string, + projectId: string, + agentId: string, +): Promise { + // Validate before building paths, to prevent path traversal. + assertValidId("project_id", projectId); + assertValidId("agent_id", agentId); + const configPath = systemConfigPath(root, projectId, agentId); + if (!(await fileExists(configPath))) { + throw new Error(`Agent State config not found: ${configPath} (the Agent does not exist).`); + } + const parsed = parseYaml(await fs.readFile(configPath, "utf8")) as unknown; + const prev = ( + parsed !== null && typeof parsed === "object" ? parsed : {} + ) as Partial; + const next: SystemConfig = { + ...(typeof prev.name === "string" ? { name: prev.name } : {}), + ...(typeof prev.description === "string" ? { description: prev.description } : {}), + ...defaultSystemConfig(), + version: agentStateVersion(prev), + }; + await fs.writeFile(configPath, stringifyYaml(next), "utf8"); + return next; +} + /** * The vault key-name list: the replacement value for `{{VAULT_KEYS}}`, one `- KEY` per line; * returns an empty string when there are no keys. @@ -353,7 +396,8 @@ export function skillMetadataSection(skills: SkillMetadata[]): string { * provided): this lets the model know which APIs requiring a key it can call; values are never * injected. `{{SKILL_METADATA}}` is replaced with the installed Skills' metadata lines (an empty * string if empty/not provided). A custom template that removes a placeholder gets no - * corresponding content injected. + * corresponding content injected. `{{PROJECT_DIR}}` resolves to the Project directory — + * the app data root the default prompt labels "App Data Dir". * Docs: /docs/configuration § "System prompt placeholders". */ export function assembleSystemPrompt( diff --git a/packages/core/src/state/default-config.ts b/packages/core/src/state/default-config.ts index 7486c14..3caf0b6 100644 --- a/packages/core/src/state/default-config.ts +++ b/packages/core/src/state/default-config.ts @@ -14,7 +14,7 @@ * * Placeholders (`{{...}}`) appear only in the trailing injection zones (AGENTS.md / Vault / * Skills / Environment); elsewhere the body uses angle-bracket notation such as - * \`\`, \`\`, \`\` — these are **not substituted**; the model + * \`\`, \`\`, \`\` — these are **not substituted**; the model * fills in the actual values from the Environment section itself. */ import type { MCPServerConfig, ThinkingLevelName, ToolDefinitionConfig } from "../interfaces.js"; @@ -27,6 +27,7 @@ export const SKILL_METADATA_PLACEHOLDER = "{{SKILL_METADATA}}"; export const SESSION_ID_PLACEHOLDER = "{{SESSION_ID}}"; export const CWD_PLACEHOLDER = "{{CWD}}"; export const AGENT_ID_PLACEHOLDER = "{{AGENT_ID}}"; +/** The Project directory — PenguinHarness's app data root, exposed in the default prompt as the "App Data Dir" Environment line (deliberately not called a project/task directory there). */ export const PROJECT_DIR_PLACEHOLDER = "{{PROJECT_DIR}}"; export const PROVIDER_PLACEHOLDER = "{{PROVIDER}}"; export const MODEL_ID_PLACEHOLDER = "{{MODEL_ID}}"; @@ -113,17 +114,18 @@ Some user-side messages are system-synthesized records, not user text to answer - \`\`: earlier conversation was compacted. This summary replaced the raw transcript and is its only record; treat it as established context and continue the task from it. # File system -- Angle-bracket markers such as \`\`, \`\` and \`\` are not literal paths — substitute the matching values from the Environment section. +- Angle-bracket markers such as \`\`, \`\` and \`\` are not literal paths — substitute the matching values from the Environment section. - You run inside the user's working folder (\`CWD\` in Environment). -- The project directory is \`\`; every agent of this project lives under \`/agents/\`, so another agent's assets are at \`/agents//agent_state/\`. -- Your own Agent State is \`/agents//agent_state/\` — it holds your assets such as \`skills/\`, and its \`AGENTS.md\` is already included in your context. Reach these paths directly. -- For temporary and scratch files, create a subdirectory named after the current Session ID under your scratchpad: \`/agents//scratchpad//\`. Build intermediates there, but always place final deliverables in the workspace (under \`CWD\`) — files left in the scratchpad are not part of your output. +- The App Data Dir is PenguinHarness's application data root: it holds every agent's data files (\`/agents//agent_state/\` and friends) plus the project-level data files. It is NOT the current task's directory and was not provided by the user — never treat its contents as task input, and never place task deliverables there; the working folder is \`CWD\`. +- Another agent's assets are at \`/agents//agent_state/\`. +- Your own Agent State is \`/agents//agent_state/\` — it holds your assets such as \`skills/\`, and its \`AGENTS.md\` is already included in your context. Reach these paths directly. +- For temporary and scratch files, create a subdirectory named after the current Session ID under your scratchpad: \`/agents//scratchpad//\`. Build intermediates there, but always place final deliverables in the workspace (under \`CWD\`) — files left in the scratchpad are not part of your output. - When you create or update a file in the workspace, mention its workspace-relative path in backticks (e.g. \`src/app.py\`) in your reply, so the user can open it from the message. -- Never read, copy, print or otherwise access \`/.project_config.toml\` or any agent's \`agent_state/.vault.toml\` — they hold the user's API keys and other secrets, which are none of your business. Configuration is CLI-only: change models or credentials with \`penguin config ...\` commands. If a task seems to require these files, say so and ask the user instead. +- Never read, copy, print or otherwise access \`.project_config.toml\` directly under the App Data Dir, or any agent's \`agent_state/.vault.toml\` — they hold the user's API keys and other secrets, which are none of your business. Configuration is CLI-only: change models or credentials with \`penguin config ...\` commands. If a task seems to require these files, say so and ask the user instead. # Suggested workflows These are recommendations, not requirements; adapt them as the task demands. -- For a long-horizon task, first write a plan in Markdown to \`/agents//scratchpad//PLAN.md\`, containing a task overview and an itemized step-by-step plan; update it after each completed step to keep execution consistent. +- For a long-horizon task, first write a plan in Markdown to \`/agents//scratchpad//PLAN.md\`, containing a task overview and an itemized step-by-step plan; update it after each completed step to keep execution consistent. - Delegate self-contained subtasks to other agents with the \`run_subagent\` tool; dispatch independent subtasks in parallel. Start every delegation prompt with your own agent id (e.g. "Caller agent: ") and name the skill the subagent should use when the task matches one. Subagents share your Workspace — exchange data through files. If \`run_subagent\` is not in your tool list, you are the subagent: do the work yourself. - To visit web pages, prefer Playwright when installed; otherwise \`curl\`. When building a web app or frontend, prefer React. @@ -138,14 +140,14 @@ The vault holds this agent's per-agent secrets (agent_state/.vault.toml). Each e {{VAULT_KEYS}} # Skills -Skills are reusable instruction packages stored under /agents//agent_state/skills//SKILL.md. There is no skill tool: when a task matches an installed skill below, or the user asks to use one (a message may start with a block listing skill names), first read that skill's SKILL.md in full with a shell command, then follow it. If a request only names a skill without a concrete task, ask the user what they need before starting. +Skills are reusable instruction packages stored under /agents//agent_state/skills//SKILL.md. There is no skill tool: when a task matches an installed skill below, or the user asks to use one (a message may start with a block listing skill names), first read that skill's SKILL.md in full with a shell command, then follow it. If a request only names a skill without a concrete task, ask the user what they need before starting. {{SKILL_METADATA}} # Environment - Platform: {{PLATFORM}} - OS Version: {{OS_VERSION}} - Date: {{DATE}} -- Project Dir: {{PROJECT_DIR}} +- App Data Dir: {{PROJECT_DIR}} - Agent ID: {{AGENT_ID}} - CWD: {{CWD}} - Provider: {{PROVIDER}} diff --git a/packages/core/src/state/paths.ts b/packages/core/src/state/paths.ts index bd81ab2..a391bff 100644 --- a/packages/core/src/state/paths.ts +++ b/packages/core/src/state/paths.ts @@ -29,9 +29,17 @@ export function projectDir(root: string, projectId: string): string { return path.join(root, projectId); } +/** + * `/agents` from an already-resolved Project directory path — the single + * definition point of the agents-container layout. + */ +export function agentsDirFrom(projectDirPath: string): string { + return path.join(projectDirPath, "agents"); +} + /** `/agents`, the container directory holding every Agent in the Project. */ export function agentsDir(root: string, projectId: string): string { - return path.join(projectDir(root, projectId), "agents"); + return agentsDirFrom(projectDir(root, projectId)); } /** `/agents/`. */ diff --git a/packages/core/test/agent-lifecycle.test.ts b/packages/core/test/agent-lifecycle.test.ts index a8f4a76..5bc353d 100644 --- a/packages/core/test/agent-lifecycle.test.ts +++ b/packages/core/test/agent-lifecycle.test.ts @@ -4,7 +4,7 @@ * - sessionId looks like `session-YYYY-MM-DD-HH-mm-ss-<8-digit hex>` (local time, zero-padded fields). * - createSession no longer creates any `.penguin` symlink inside the Workspace, nor touches existing * Workspace files; the model reaches Agent State and other absolute paths directly by combining the - * Project Dir / Agent ID placeholders from the system prompt. + * App Data Dir / Agent ID placeholders from the system prompt. */ import fs from "node:fs/promises"; import os from "node:os"; @@ -92,14 +92,14 @@ describe("Agent.createSession session id + no .penguin symlink", () => { expect(process.listenerCount("exit") - before).toBe(0); }); - it("injects Project Dir and Agent ID into the assembled system prompt", async () => { + it("injects App Data Dir and Agent ID into the assembled system prompt", async () => { const agent = await createAgent(); const ws = path.join(tmpRoot, "ws"); await fs.mkdir(ws, { recursive: true }); const session = await agent.createSession({ workspaceDir: ws }); const prompt = (session.metaMessage.payload as { system_prompt: string }).system_prompt; expect(prompt).toContain(`Agent ID: ${agent.state.agentId}`); - expect(prompt).toContain(`Project Dir: ${projectDir(tmpRoot, agent.state.projectId)}`); + expect(prompt).toContain(`App Data Dir: ${projectDir(tmpRoot, agent.state.projectId)}`); expect(prompt).not.toContain(".penguin"); }); }); diff --git a/packages/core/test/builtin-agents.test.ts b/packages/core/test/builtin-agents.test.ts index a71fbff..ac26cc5 100644 --- a/packages/core/test/builtin-agents.test.ts +++ b/packages/core/test/builtin-agents.test.ts @@ -114,8 +114,8 @@ describe("provisionProjectAgents", () => { }); }); -describe("Project Dir / Agent ID placeholders", () => { - it("assembleSystemPrompt injects Project Dir and Agent ID (Skill lookup uses project-relative paths, no .penguin dependency)", async () => { +describe("App Data Dir / Agent ID placeholders", () => { + it("assembleSystemPrompt injects App Data Dir and Agent ID (Skill lookup uses app-data-dir-relative paths, no .penguin dependency)", async () => { const state = await loadOrInitAgentState({ agentId: "env_agent" }); const prompt = assembleSystemPrompt(state, { sessionId: "session-x", @@ -129,15 +129,18 @@ describe("Project Dir / Agent ID placeholders", () => { date: "2026-07-08", }); expect(prompt).toContain("Agent ID: env_agent"); - expect(prompt).toContain("Project Dir: /tmp/proj"); + expect(prompt).toContain("App Data Dir: /tmp/proj"); expect(prompt).not.toContain("{{AGENT_ID}}"); expect(prompt).not.toContain("{{PROJECT_DIR}}"); - // Skill execution conventions are built from project-relative paths (no longer reference .penguin). + // Skill execution conventions are built from app-data-dir-relative paths (no longer reference .penguin). expect(prompt).not.toContain(".penguin"); - // Agent State, scratchpad and Skills are addressed under the Project's agents/ container. - expect(prompt).toContain("/agents//agent_state/"); - expect(prompt).toContain("/agents//agent_state/skills/"); - // The pre-agents/ layout (an agent directly under the Project dir) must never be handed to the model. - expect(prompt).not.toContain("//"); + // Agent State, scratchpad and Skills are addressed under the App Data Dir's agents/ container. + expect(prompt).toContain("/agents//agent_state/"); + expect(prompt).toContain("/agents//agent_state/skills/"); + // The pre-agents/ layout (an agent directly under the data root) must never be handed to the model. + expect(prompt).not.toContain("//"); + // The value stays the project dir, but the confusing "Project Dir" label must not. + expect(prompt).not.toContain(""); + expect(prompt).not.toContain("Project Dir:"); }); }); diff --git a/packages/core/test/state.test.ts b/packages/core/test/state.test.ts index 804bfc4..31c8838 100644 --- a/packages/core/test/state.test.ts +++ b/packages/core/test/state.test.ts @@ -26,6 +26,8 @@ import { buildToolConfig, selectBuiltinToolsForModel, defaultProjectConfig, + defaultSystemConfig, + resetSystemConfigToDefaults, getModel, isValidVaultKey, loadOrInitAgentState, @@ -313,6 +315,12 @@ describe("assembleSystemPrompt", () => { expect(prompt).not.toContain(PLATFORM_PLACEHOLDER); expect(prompt).not.toContain(OS_VERSION_PLACEHOLDER); expect(prompt).not.toContain(DATE_PLACEHOLDER); + expect(prompt).not.toContain(PROJECT_DIR_PLACEHOLDER); + // The project dir is labeled "App Data Dir" (the app data root, not the task's + // directory) — the raw "Project Dir" label must never reach the model. + expect(prompt).toContain("App Data Dir: /tmp/proj"); + expect(prompt).not.toContain("Project Dir:"); + expect(prompt).not.toContain(""); }); it("default prompt carries the port and API-key guardrails", async () => { @@ -498,7 +506,7 @@ describe("assembleSystemPrompt", () => { expect(prompt).toContain("Session ID: session-test-1"); expect(prompt).toContain("CWD: /tmp/penguin-ws"); expect(prompt).toContain("Agent ID: agent-x"); - expect(prompt).toContain("Project Dir: /tmp/proj"); + expect(prompt).toContain("App Data Dir: /tmp/proj"); expect(prompt).toContain("Provider: openai"); expect(prompt).toContain("Model ID: gpt-5.5"); expect(prompt).toContain("Platform:"); @@ -506,8 +514,8 @@ describe("assembleSystemPrompt", () => { expect(prompt).toContain("Date: 2026-06-30"); expect(prompt.indexOf("Platform:")).toBeLessThan(prompt.indexOf("OS Version:")); expect(prompt.indexOf("OS Version:")).toBeLessThan(prompt.indexOf("Date:")); - expect(prompt.indexOf("Date:")).toBeLessThan(prompt.indexOf("Project Dir:")); - expect(prompt.indexOf("Project Dir:")).toBeLessThan(prompt.indexOf("Agent ID:")); + expect(prompt.indexOf("Date:")).toBeLessThan(prompt.indexOf("App Data Dir:")); + expect(prompt.indexOf("App Data Dir:")).toBeLessThan(prompt.indexOf("Agent ID:")); expect(prompt.indexOf("Agent ID:")).toBeLessThan(prompt.indexOf("CWD:")); expect(prompt.indexOf("CWD:")).toBeLessThan(prompt.indexOf("Provider:")); expect(prompt.indexOf("Provider:")).toBeLessThan(prompt.indexOf("Model ID:")); @@ -515,6 +523,81 @@ describe("assembleSystemPrompt", () => { }); }); +describe("resetSystemConfigToDefaults", () => { + it("replaces everything with the current defaults, keeping only name/description/version", async () => { + await loadOrInitAgentState(); + const configPath = systemConfigPath(tmpRoot, DEFAULT_PROJECT_ID, DEFAULT_AGENT_ID); + // An old on-disk config: custom prompt/runtime/tools plus a key outside the schema. + await fs.writeFile( + configPath, + [ + "name: Custom Name", + "description: Custom description", + "version: 7", + "system_prompt: Custom prompt with {{PROJECT_DIR}}", + "max_turns: 5", + "model:", + " max_tokens: 1234", + "compaction:", + " mode: discard", + "tools:", + " builtin: []", + " mcpServers:", + " - name: custom-mcp", + " config: {}", + "custom_extra_key: should-be-dropped", + ].join("\n"), + "utf8", + ); + + const written = await resetSystemConfigToDefaults( + tmpRoot, + DEFAULT_PROJECT_ID, + DEFAULT_AGENT_ID, + ); + // Identity fields survive; everything else is the current default. + expect(written.name).toBe("Custom Name"); + expect(written.description).toBe("Custom description"); + expect(written.version).toBe(7); + const defaults = defaultSystemConfig(); + expect(written.system_prompt).toBe(defaults.system_prompt); + expect(written.max_turns).toBe(defaults.max_turns); + expect(written.model).toEqual(defaults.model); + expect(written.compaction).toEqual(defaults.compaction); + expect(written.tools).toEqual(defaults.tools); + + // The file on disk round-trips to the same object; out-of-schema keys are gone. + const reloaded = await loadOrInitAgentState(); + expect(reloaded.systemConfig).toEqual(written); + expect("custom_extra_key" in reloaded.systemConfig).toBe(false); + expect(reloaded.systemConfig.system_prompt).toContain("App Data Dir: {{PROJECT_DIR}}"); + }); + + it("normalizes an invalid version to 1 and keeps a missing name/description absent", async () => { + await loadOrInitAgentState(); + const configPath = systemConfigPath(tmpRoot, DEFAULT_PROJECT_ID, DEFAULT_AGENT_ID); + await fs.writeFile(configPath, "system_prompt: old\nversion: nonsense\n", "utf8"); + const written = await resetSystemConfigToDefaults( + tmpRoot, + DEFAULT_PROJECT_ID, + DEFAULT_AGENT_ID, + ); + expect(written.version).toBe(1); + expect("name" in written).toBe(false); + expect("description" in written).toBe(false); + }); + + it("throws for a nonexistent Agent instead of initializing one", async () => { + await expect( + resetSystemConfigToDefaults(tmpRoot, DEFAULT_PROJECT_ID, "ghost_agent"), + ).rejects.toThrow(/not found/); + // No directory is created as a side effect. + await expect( + fs.access(agentStateDir(tmpRoot, DEFAULT_PROJECT_ID, "ghost_agent")), + ).rejects.toThrow(); + }); +}); + describe("project-config round trip", () => { it("returns default config when file is absent (without writing)", async () => { const cfg = await loadProjectConfig(tmpRoot, DEFAULT_PROJECT_ID); diff --git a/packages/docs/content/configuration.en.md b/packages/docs/content/configuration.en.md index e3d2e3d..b91c02c 100644 --- a/packages/docs/content/configuration.en.md +++ b/packages/docs/content/configuration.en.md @@ -134,6 +134,8 @@ compaction: # parameters JSON Schema) for every tool you keep — see Tools & Approval. ``` +An existing Agent always runs with its on-disk config verbatim — newer code defaults are never merged in automatically. To adopt the current defaults (for example an updated built-in system prompt), use the settings page's **Restore default configuration** action: like a skill update it overwrites the existing configuration — custom system prompt, tool list, model/compaction settings and MCP Servers — keeping only `name`, `description` and `version`. + ### System prompt placeholders `system_prompt` is the only template with placeholder substitution. Available placeholders: @@ -146,13 +148,15 @@ compaction: | `{{PLATFORM}}` | Runtime platform | | `{{OS_VERSION}}` | Operating system version | | `{{DATE}}` | Current date | -| `{{PROJECT_DIR}}` | Project directory | +| `{{PROJECT_DIR}}` | App Data Dir: the PenguinHarness app data root (the Project directory) | | `{{AGENT_ID}}` | Agent id | | `{{CWD}}` | Workspace path | | `{{PROVIDER}}` | Model provider group | | `{{MODEL_ID}}` | Upstream model id | | `{{SESSION_ID}}` | Session id | +`{{PROJECT_DIR}}` is surfaced to the model as the **App Data Dir**: PenguinHarness's application data root, holding every Agent's data files (`agents//…`) and the project-level data — deliberately not described as a project or task directory, so the model does not mistake it for the task's working directory (`CWD`). + `agent_state/AGENTS.md` is the developer-editable instruction file, injected via `{{AGENTS_MD}}` and empty by default — it is also the file an optimizer edits most (see [Self-Improvement](/self-improvement)). ## Vault diff --git a/packages/docs/content/configuration.zh.md b/packages/docs/content/configuration.zh.md index 15b8da7..351b6c7 100644 --- a/packages/docs/content/configuration.zh.md +++ b/packages/docs/content/configuration.zh.md @@ -134,6 +134,8 @@ compaction: # 参见「工具与审批」页。 ``` +既有 Agent 始终按其磁盘上的配置原样运行——更新后的代码默认值不会自动合并。要采用当前默认值(例如更新后的内置系统提示词),可使用设置页的**还原为默认配置**操作:与 Skill 更新同语义,会用当前默认值覆盖现有配置——自定义系统提示词、工具列表、模型/压缩参数与 MCP Server——仅保留 `name`、`description` 与 `version`。 + ### 系统提示词占位符 `system_prompt` 是唯一进行占位符替换的模板,可用占位符: @@ -146,13 +148,15 @@ compaction: | `{{PLATFORM}}` | 运行平台 | | `{{OS_VERSION}}` | 操作系统版本 | | `{{DATE}}` | 当前日期 | -| `{{PROJECT_DIR}}` | Project 目录 | +| `{{PROJECT_DIR}}` | App Data Dir:PenguinHarness 应用数据根目录(即 Project 目录) | | `{{AGENT_ID}}` | Agent id | | `{{CWD}}` | Workspace 路径 | | `{{PROVIDER}}` | 模型 provider 分组 | | `{{MODEL_ID}}` | 上游模型 id | | `{{SESSION_ID}}` | Session id | +`{{PROJECT_DIR}}` 在提示词中以 **App Data Dir** 名义暴露给模型:PenguinHarness 的应用数据根目录,存放全部 Agent 的数据文件(`agents//…`)与 Project 级数据——特意不以 Project/任务目录的口径描述,避免模型将其误认为本次任务的工作目录(`CWD`)。 + `agent_state/AGENTS.md` 是开发者可编辑的指令文件,经 `{{AGENTS_MD}}` 注入系统提示词,缺省为空——它也是优化器最常改动的文件(见[自我进化](/self-improvement))。 ## Vault diff --git a/packages/docs/content/web-app.en.md b/packages/docs/content/web-app.en.md index 42ee302..d029ee0 100644 --- a/packages/docs/content/web-app.en.md +++ b/packages/docs/content/web-app.en.md @@ -61,7 +61,7 @@ The list page creates and deletes Agents; clicking through opens the `/agents/:a | Tab | Contents | | --- | --- | -| Overview | Basic info, plus export / import of Agent State snapshots | +| Overview | Basic info, export / import of Agent State snapshots, and restoring the default configuration (overwrites customizations, keeping only name/description) | | Prompt | AGENTS.md and system_prompt | | Runtime | Runtime parameters such as max_turns, model.*, compaction.* | | Tools | Built-in tool table and MCP server JSON configuration | diff --git a/packages/docs/content/web-app.zh.md b/packages/docs/content/web-app.zh.md index aeafb25..190cd9e 100644 --- a/packages/docs/content/web-app.zh.md +++ b/packages/docs/content/web-app.zh.md @@ -61,7 +61,7 @@ penguin web | 标签页 | 内容 | | --- | --- | -| Overview | 基本信息,以及 Agent State 快照的导出 / 导入 | +| Overview | 基本信息、Agent State 快照的导出 / 导入,以及还原为默认配置(覆盖自定义内容,仅保留名称与描述) | | Prompt | AGENTS.md 与 system_prompt | | Runtime | max_turns、model.*、compaction.* 等运行参数 | | Tools | 内置工具表格与 MCP Server 的 JSON 配置 | diff --git a/packages/server/src/http/routes/agent-config.ts b/packages/server/src/http/routes/agent-config.ts index c9c7957..d1e1f56 100644 --- a/packages/server/src/http/routes/agent-config.ts +++ b/packages/server/src/http/routes/agent-config.ts @@ -1,6 +1,8 @@ /** * Agent config routes (reads/writes system_config.yaml and AGENTS.md): - * GET|PUT /api/projects/:p/agents/:a/config. Members can read and write (unrestricted). + * GET|PUT /api/projects/:p/agents/:a/config, plus POST …/config/reset to adopt the + * current default config (keeps only name/description/version — the config-side + * analogue of a skill update). Members can read and write (unrestricted). */ import { Hono } from "hono"; import type { AgentConfigResponse, AgentConfigUpdateRequest } from "../../api/types.js"; @@ -46,5 +48,20 @@ export function agentConfigRoutes(deps: AppDeps): Hono { } satisfies AgentConfigResponse); }); + // Overwrite system_config.yaml with the current defaults (see AgentConfigService.resetConfig + // for the exact semantics); same authorization as PUT, and responds like GET/PUT with the + // fresh config so the client can refresh in place. + app.post("/reset", async (c) => { + const projectId = requireValidId(c, "projectId"); + const agentId = requireValidId(c, "agentId"); + deps.projectService.requireProjectAccess(c.var.user.userId, projectId); + await deps.agentConfigService.resetConfig(projectId, agentId); + const view = await deps.agentConfigService.getConfig(projectId, agentId); + return c.json({ + ...view, + activeSessionCount: deps.manager.activeCountForAgent(projectId, agentId), + } satisfies AgentConfigResponse); + }); + return app; } diff --git a/packages/server/src/services/agent-config-service.ts b/packages/server/src/services/agent-config-service.ts index 40adfd4..99fd92f 100644 --- a/packages/server/src/services/agent-config-service.ts +++ b/packages/server/src/services/agent-config-service.ts @@ -16,6 +16,7 @@ import { VAULT_VALUE_MAX_LENGTH, isValidVaultKey, loadAgentVault, + resetSystemConfigToDefaults, saveAgentVault, systemConfigPath, } from "@prismshadow/penguin-core"; @@ -173,6 +174,19 @@ export class AgentConfigService { } } + /** + * Overwrites system_config.yaml with the current code defaults (core's + * resetSystemConfigToDefaults) — same semantics as updating an installed skill: + * only the identity fields (name / description / version) survive; the system + * prompt, max_turns, model/compaction settings and the tool list (incl. MCP + * servers) become the current defaults. AGENTS.md, skills and the vault are + * untouched. + */ + async resetConfig(projectId: string, agentId: string): Promise { + await this.requireExists(projectId, agentId); + await resetSystemConfigToDefaults(this.root, projectId, agentId); + } + private async applyConfigUpdate( projectId: string, agentId: string, diff --git a/packages/server/test/agent-config-reset.test.ts b/packages/server/test/agent-config-reset.test.ts new file mode 100644 index 0000000..d3663e1 --- /dev/null +++ b/packages/server/test/agent-config-reset.test.ts @@ -0,0 +1,89 @@ +/** + * POST /api/projects/:projectId/agents/:agentId/config/reset — overwrites + * system_config.yaml with the current code defaults, keeping only the identity fields + * (name / description / version); the config-side analogue of a skill update. Same + * member-level authorization as PUT config; a non-member gets 404 without a write. + */ +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { defaultSystemConfig } from "@prismshadow/penguin-core"; +import type { AgentConfigResponse, ProjectCreateResponse } from "../src/api/types.js"; +import { apiClient, createTestApp, provisionUser } from "./helpers.js"; +import type { TestApp } from "./helpers.js"; + +describe("POST agent config reset", () => { + let t: TestApp; + let alice: ReturnType; + let projectId: string; + const configUrl = () => `/api/projects/${projectId}/agents/default_agent/config`; + + beforeEach(async () => { + t = await createTestApp(); + const a = await provisionUser(t.app, "alice"); + alice = apiClient(t.app, a.cookie); + // Project ids are username-prefixed (`alice-…`), same as the other route tests. + const created = (await ( + await alice.post("/api/projects", { projectId: "alice-reset", name: "Reset project" }) + ).json()) as ProjectCreateResponse; + projectId = created.project.projectId; + }); + afterEach(async () => { + await t.cleanup(); + }); + + it("restores the defaults while preserving name / description / version", async () => { + // Customize everything the reset is supposed to overwrite, plus the kept fields. + const put = await alice.put(configUrl(), { + config: { + name: "Custom Name", + description: "Custom description", + systemPrompt: "CUSTOM PROMPT", + maxTurns: 3, + model: { maxTokens: 1234 }, + compaction: { mode: "discard" }, + toolsBuiltin: [], + mcpServers: [{ name: "custom-mcp", config: {} }], + }, + }); + expect(put.status).toBe(200); + + const res = await alice.post(`${configUrl()}/reset`); + expect(res.status).toBe(200); + const body = (await res.json()) as AgentConfigResponse; + const defaults = defaultSystemConfig(); + + // Identity fields survive. + expect(body.config.name).toBe("Custom Name"); + expect(body.config.description).toBe("Custom description"); + expect(body.config.version).toBe(1); + // Everything else is back to the current defaults. + expect(body.config.systemPrompt).toBe(defaults.system_prompt); + expect(body.config.systemPrompt).toContain("App Data Dir: {{PROJECT_DIR}}"); + expect(body.config.maxTurns).toBe(defaults.max_turns); + expect(body.config.model?.maxTokens).toBe(defaults.model?.max_tokens); + expect(body.config.compaction?.mode).toBe("summarize"); + expect(body.config.toolsBuiltin.map((tool) => tool.name)).toEqual( + (defaults.tools?.builtin ?? []).map((tool) => tool.name), + ); + expect(body.config.mcpServers).toEqual([]); + + // A later GET sees the same reset state (it was persisted, not just echoed). + const got = (await (await alice.get(configUrl())).json()) as AgentConfigResponse; + expect(got.config.systemPrompt).toBe(defaults.system_prompt); + expect(got.config.name).toBe("Custom Name"); + }); + + it("404 for a nonexistent agent (no initialization side effect)", async () => { + const res = await alice.post(`/api/projects/${projectId}/agents/ghost/config/reset`); + expect(res.status).toBe(404); + }); + + it("404 for a non-member; the config is not modified", async () => { + const before = (await (await alice.get(configUrl())).json()) as AgentConfigResponse; + const m = await provisionUser(t.app, "mallory"); + const mallory = apiClient(t.app, m.cookie); + const res = await mallory.post(`${configUrl()}/reset`); + expect(res.status).toBe(404); + const after = (await (await alice.get(configUrl())).json()) as AgentConfigResponse; + expect(after.systemConfigYaml).toBe(before.systemConfigYaml); + }); +}); diff --git a/packages/skills/skills/agent-creation/SKILL.md b/packages/skills/skills/agent-creation/SKILL.md index 74f408c..97a91a8 100644 --- a/packages/skills/skills/agent-creation/SKILL.md +++ b/packages/skills/skills/agent-creation/SKILL.md @@ -3,8 +3,8 @@ name: agent-creation description: Turn a user requirement into a concrete agent — write the target agent's AGENTS.md and install the skills it needs. short_description: Turn a requirement into a working agent. short_description_zh: 把需求变成可用的 Agent。 -version: 2 -updated: 2026-07-20T13:00:00Z +version: 5 +updated: 2026-07-25T00:00:00Z --- # Agent Creation @@ -17,12 +17,12 @@ If the user's message only invokes this skill (e.g. "use agent-creation skill") ## Locate the target agent -All agents of this project live side by side in the project directory: +All agents of this project live side by side under `agents/` in the App Data Dir: ```bash -PROJECT_DIR="" # the Project Dir value from your Environment section -ls "$PROJECT_DIR/agents" # existing agents (each is a folder here) -TARGET="$PROJECT_DIR/agents/" # the agent to configure +APP_DATA_DIR="" # the App Data Dir value from your Environment section +ls "$APP_DATA_DIR/agents" # existing agents (each is a folder here) +TARGET="$APP_DATA_DIR/agents/" # the agent to configure ``` An agent directory contains `agent_state/` (`system_config.yaml`, `AGENTS.md`, `skills/`, `memory/`, `tools/`) plus `scratchpad/` — and `traces/`, which appears once the agent has run at least once. @@ -73,7 +73,7 @@ Prefer configuring an agent the user already created. If you must create one fro ```bash mkdir -p "$TARGET/agent_state/skills" "$TARGET/agent_state/memory" "$TARGET/agent_state/tools" "$TARGET/scratchpad" -cp "$PROJECT_DIR/agents/default_agent/agent_state/system_config.yaml" "$TARGET/agent_state/" +cp "$APP_DATA_DIR/agents/default_agent/agent_state/system_config.yaml" "$TARGET/agent_state/" ``` A new agent starts with no skills — install only what it needs. Then write its AGENTS.md, name and description as above. diff --git a/packages/skills/skills/agent-evaluation/SKILL.md b/packages/skills/skills/agent-evaluation/SKILL.md index 1b228c8..a826788 100644 --- a/packages/skills/skills/agent-evaluation/SKILL.md +++ b/packages/skills/skills/agent-evaluation/SKILL.md @@ -3,8 +3,8 @@ name: agent-evaluation description: Run and score exactly one Benchmark Case run with CLI execution, Trace provenance checks, and private Rubric isolation. short_description: Run and score one isolated Benchmark Case. short_description_zh: 隔离执行并评分一个 Benchmark Case。 -version: 1 -updated: 2026-07-17T17:08:17Z +version: 4 +updated: 2026-07-25T00:00:00Z --- # Agent Evaluation @@ -34,7 +34,7 @@ model_id: ## Validate and prepare -Resolve the Project, Test Agent, Benchmark, and Case only from the explicit request and Environment Project Dir. Reject traversal, symlink escape, or any path outside the requested Test Agent. Never read a Project configuration file, credential, or vault. +Resolve the Project, Test Agent, Benchmark, and Case only from the explicit request and Environment App Data Dir. Reject traversal, symlink escape, or any path outside the requested Test Agent. Never read a Project configuration file, credential, or vault. Require: @@ -58,7 +58,7 @@ Use an existing verified Penguin CLI or repository-local launcher already availa Run the Test Agent exactly once in the foreground with a fresh top-level Session: ```bash -PROJECT_DIR="" +PROJECT_DIR="" # the App Data Dir value from your Environment section is the project root PROJECT_ID="$(basename "$PROJECT_DIR")" PENGUIN_HOME="$(dirname "$PROJECT_DIR")" WORKSPACE="$PROJECT_DIR/agents//workspaces/" diff --git a/packages/skills/skills/agent-optimization/SKILL.md b/packages/skills/skills/agent-optimization/SKILL.md index 7d75970..fcb2d67 100644 --- a/packages/skills/skills/agent-optimization/SKILL.md +++ b/packages/skills/skills/agent-optimization/SKILL.md @@ -3,8 +3,8 @@ name: agent-optimization description: Improve an Agent State from direct feedback or versioned multi-Case Benchmark scores and score-linked Traces. short_description: Improve an Agent from feedback or measured Benchmark results. short_description_zh: 根据反馈或 Benchmark 结果改进 Agent。 -version: 1 -updated: 2026-07-17T17:08:17Z +version: 4 +updated: 2026-07-25T00:00:00Z --- # Agent Optimization @@ -21,13 +21,13 @@ Benchmark mode requires a top-level Session with `run_subagent`, a complete base A one-shot request normally names the target. A delegated request begins with `Caller agent: `, and an @-mention handoff contains ``. When one-shot mode has no explicit target, use that caller or origin; if neither exists, ask. Benchmark mode always requires an explicit Test Agent and Benchmark. -Resolve paths from the Environment's Project Dir without recursively discovering the Project: +Resolve paths from the Environment's App Data Dir without recursively discovering the Project: ```text -PROJECT_DIR = +PROJECT_DIR = PROJECT_ID = PENGUIN_HOME = -TARGET = /agents/ +TARGET = /agents/ STATE = /agent_state TRACES = /traces BENCHMARK = /benchmarks/ diff --git a/packages/skills/skills/benchmark-design/SKILL.md b/packages/skills/skills/benchmark-design/SKILL.md index c7e7ed9..d06e0f5 100644 --- a/packages/skills/skills/benchmark-design/SKILL.md +++ b/packages/skills/skills/benchmark-design/SKILL.md @@ -3,8 +3,8 @@ name: benchmark-design description: Design and calibrate a multi-Case capability Benchmark with repeated independent evaluations and a traceable baseline. short_description: Design and calibrate an Agent capability Benchmark. short_description_zh: 设计并校准 Agent 能力评测 Benchmark。 -version: 1 -updated: 2026-07-17T17:08:17Z +version: 4 +updated: 2026-07-25T00:00:00Z --- # Benchmark Design @@ -19,14 +19,14 @@ Require a Test Agent and the capability to measure. If either is missing, ask th Access only the explicit Test Agent and Benchmark paths. Do not inspect another Agent, Project configuration files, Agent Evaluator State, Evaluator Workspace, or Evaluator Trace. Consume only each Evaluator's terminal protocol response and the returned Test Session id. -Use the Environment's Project Dir and the explicit Test Agent id: +Use the Environment's App Data Dir and the explicit Test Agent id: ```text -PROJECT_DIR = +PROJECT_DIR = PROJECT_ID = PENGUIN_HOME = -TEST_AGENT_DIR = /agents/ -BENCHMARK_DIR = /agents//benchmarks/ +TEST_AGENT_DIR = /agents/ +BENCHMARK_DIR = /agents//benchmarks/ SCOREBOARD = /scoreboard.yaml ``` diff --git a/packages/skills/skills/penguin-cli/SKILL.md b/packages/skills/skills/penguin-cli/SKILL.md index 3c77017..749c3c6 100644 --- a/packages/skills/skills/penguin-cli/SKILL.md +++ b/packages/skills/skills/penguin-cli/SKILL.md @@ -3,8 +3,8 @@ name: penguin-cli description: Manage model API keys, default models and per-agent vault secrets with the penguin CLI. short_description: Manage models and secrets with the penguin CLI. short_description_zh: 用 penguin CLI 管理模型与密钥。 -version: 5 -updated: 2026-07-22T00:00:00Z +version: 8 +updated: 2026-07-25T00:00:00Z --- # Penguin CLI @@ -69,5 +69,7 @@ penguin config lang # persist the CLI language via PENGUIN_LANG in you ## Storage -- `/.project_config.toml` — the project's single hidden config file: model list, settings and per-model credentials (`api_key` etc. inlined in each model entry). Configuration is CLI-only — never read, print or hand-edit this file. -- `/agents//agent_state/.vault.toml` — that agent's vault entries, hidden file; same rule, manage it with `penguin config vault`. +Paths use ``, the App Data Dir value from your Environment section. + +- `/.project_config.toml` — the project's single hidden config file: model list, settings and per-model credentials (`api_key` etc. inlined in each model entry). Configuration is CLI-only — never read, print or hand-edit this file. +- `/agents//agent_state/.vault.toml` — that agent's vault entries, hidden file; same rule, manage it with `penguin config vault`. diff --git a/packages/skills/skills/penguin-sdk/SKILL.md b/packages/skills/skills/penguin-sdk/SKILL.md index 6120e76..6fbf1d1 100644 --- a/packages/skills/skills/penguin-sdk/SKILL.md +++ b/packages/skills/skills/penguin-sdk/SKILL.md @@ -3,8 +3,8 @@ name: penguin-sdk description: Build AI apps on the Penguin Harness SDK — self-contained projects, the createSession/run streaming loop, and a complete RAG recipe that ingests documents into a knowledge base and answers with citations behind a web UI. short_description: Build AI and RAG apps on the Penguin Harness SDK. short_description_zh: 基于 Penguin SDK 构建 AI 与 RAG 应用。 -version: 11 -updated: 2026-07-21T00:00:00Z +version: 14 +updated: 2026-07-25T00:00:00Z --- # Penguin Harness SDK @@ -23,7 +23,7 @@ If the user's message only invokes this skill (e.g. "use penguin-sdk skill") wit ## Project location -Create the app in the current workspace directory by default (the `CWD` value from your Environment section), as a self-contained project — do not place it under `` or depend on any path outside the project folder. When creating the app's agent, the data root defaults **under the working directory (CWD)** too: point `createAgent({ root })` at a directory inside the project, resolved from the source file so it stays relative: +Create the app in the current workspace directory by default (the `CWD` value from your Environment section), as a self-contained project — do not place it under `` (PenguinHarness's app data root) or depend on any path outside the project folder. When creating the app's agent, the data root defaults **under the working directory (CWD)** too: point `createAgent({ root })` at a directory inside the project, resolved from the source file so it stays relative: ```ts const agent = await createAgent({ root: path.join(import.meta.dirname, "penguin_data") }); diff --git a/packages/web/src/api/endpoints.ts b/packages/web/src/api/endpoints.ts index e179419..2676a53 100644 --- a/packages/web/src/api/endpoints.ts +++ b/packages/web/src/api/endpoints.ts @@ -179,6 +179,13 @@ export const putAgentConfig = ( { method: "PUT", body }, ); +/** Overwrite system_config.yaml with the current defaults (keeps only name/description/version). */ +export const resetAgentConfig = (projectId: string, agentId: string) => + apiFetch( + `/api/projects/${encodeURIComponent(projectId)}/agents/${encodeURIComponent(agentId)}/config/reset`, + { method: "POST" }, + ); + export const getAgentTraces = (projectId: string, agentId: string) => apiFetch( `/api/projects/${encodeURIComponent(projectId)}/agents/${encodeURIComponent(agentId)}/traces`, diff --git a/packages/web/src/features/agents/agent-settings-page.tsx b/packages/web/src/features/agents/agent-settings-page.tsx index f2ec1e0..1961e6d 100644 --- a/packages/web/src/features/agents/agent-settings-page.tsx +++ b/packages/web/src/features/agents/agent-settings-page.tsx @@ -1,7 +1,7 @@ /** * Agent settings page: six tabs — * Overview (name/description/State path/active count/State version + snapshot - * export-import), Prompt (AGENTS.md and system_prompt editors + placeholder + * export-import + restore default configuration), Prompt (AGENTS.md and system_prompt editors + placeholder * reference), Runtime (max_turns, model.*, compaction.*), Tools (editable built-in * tools table, MCP Server read-only JSON), Vault (vault-tab.tsx), Schedule * (schedules-tab.tsx). @@ -116,6 +116,13 @@ export function AgentSettingsPage() { [load, reloadAgents], ); + /** Config reset succeeded: the whole system_config.yaml was replaced, so reload every tab's data (and the list's tool counts). */ + const onConfigReset = useCallback(() => { + toastSuccess(S.agent.resetConfigDone); + load(); + void reloadAgents(); + }, [load, reloadAgents]); + const save = useCallback( async (update: AgentConfigUpdateRequest) => { if (!projectId || !agentId) return; @@ -177,7 +184,13 @@ export function AgentSettingsPage() {
{tab === "overview" && ( - + )} {tab === "prompt" && } {tab === "runtime" && } @@ -204,11 +217,13 @@ function OverviewTab({ agentId, onSave, onImported, + onConfigReset, }: { data: AgentConfigResponse; agentId: string; onSave: SaveFn; onImported: (version: number) => void; + onConfigReset: () => void; }) { const { currentProject } = useProject(); const projectId = currentProject?.projectId ?? null; @@ -219,8 +234,24 @@ function OverviewTab({ const [importError, setImportError] = useState(null); // base64 of the snapshot package pending confirmation for a version conflict (409 version_conflict); non-null shows the confirm modal. const [conflict, setConflict] = useState(null); + const [resetOpen, setResetOpen] = useState(false); + const [resetting, setResetting] = useState(false); const { requestSave, element: saveConfirm } = useSaveConfirm(); + const runReset = async () => { + if (!projectId) return; + setResetting(true); + try { + await api.resetAgentConfig(projectId, agentId); + setResetOpen(false); + onConfigReset(); + } catch (e) { + toastError(apiErrorText(e)); + } finally { + setResetting(false); + } + }; + const submit = () => { const config: NonNullable = {}; if (name.trim() !== (data.config.name ?? "")) config.name = name.trim(); @@ -336,6 +367,15 @@ function OverviewTab({ )}
+ {/* Restore default configuration: overwrite system_config.yaml with the current defaults (name/description/version kept) — the config-side analogue of a skill update. */} +
+

{S.agent.resetConfigTitle}

+

{S.agent.resetConfigDesc}

+ +
+ @@ -353,6 +393,18 @@ function OverviewTab({ >

{S.agent.importConflictBody}

+ + {/* Reset confirmation: overwriting customizations with the defaults is destructive, so it keeps the danger tone. */} + setResetOpen(false)} + onConfirm={() => void runReset()} + confirmLabel={S.agent.resetConfigAction} + > +

{S.agent.resetConfigConfirmBody}

+
); } diff --git a/packages/web/src/lib/strings-en.ts b/packages/web/src/lib/strings-en.ts index f5eab51..cc3901c 100644 --- a/packages/web/src/lib/strings-en.ts +++ b/packages/web/src/lib/strings-en.ts @@ -190,12 +190,18 @@ export const en: Strings = { placeholders: [ ["{{AGENTS_MD}}", "Injects the AGENTS.md content"], ["{{VAULT_KEYS}}", "Injects the vault key-name section (empty when no keys)"], + ["{{SKILL_METADATA}}", "Injects the installed skills' metadata lines (empty when none)"], ["{{PLATFORM}}", "Runtime platform"], ["{{OS_VERSION}}", "Operating system version"], ["{{DATE}}", "Current date"], - ["{{CWD}}", "Absolute Workspace path"], + [ + "{{PROJECT_DIR}}", + "PenguinHarness app data root — all agents' data and project-level data; not the task working directory", + ], ["{{AGENT_ID}}", "Current agent id"], - ["{{PROJECT_DIR}}", "Absolute Project directory (Agent State/scratchpad derive from it)"], + ["{{CWD}}", "Absolute Workspace path"], + ["{{PROVIDER}}", "Model provider group"], + ["{{MODEL_ID}}", "Upstream model id"], ["{{SESSION_ID}}", "Current Session id"], ] as ReadonlyArray, maxTurns: "max_turns (max turns per Task, -1 = unlimited)", @@ -265,6 +271,13 @@ export const en: Strings = { importConflictTitle: "Version conflict", importConflictBody: "The snapshot's version is not newer than the current one; importing will overwrite the existing Agent State. Continue?", + resetConfigTitle: "Restore default configuration", + resetConfigDesc: + "Restores system_config.yaml to the current built-in defaults (same semantics as a skill update): the custom system prompt, tool list, model/compaction settings and MCP servers are overwritten; only name, description and the State version are kept.", + resetConfigAction: "Restore default configuration", + resetConfigConfirmBody: + "This overwrites the agent's existing configuration with the current defaults: the custom system prompt, tool list, model/compaction settings and MCP servers are all replaced, keeping only name and description. Like a skill update this cannot be undone. Continue?", + resetConfigDone: "Configuration restored to the current defaults", }, models: { diff --git a/packages/web/src/lib/strings.ts b/packages/web/src/lib/strings.ts index e57b047..1bee768 100644 --- a/packages/web/src/lib/strings.ts +++ b/packages/web/src/lib/strings.ts @@ -181,12 +181,18 @@ export const zh = { placeholders: [ ["{{AGENTS_MD}}", "注入 AGENTS.md 内容"], ["{{VAULT_KEYS}}", "注入密钥保险柜的键名小节(无键时为空)"], + ["{{SKILL_METADATA}}", "注入已安装 Skill 的元数据行(无 Skill 时为空)"], ["{{PLATFORM}}", "运行平台"], ["{{OS_VERSION}}", "操作系统版本"], ["{{DATE}}", "当前日期"], - ["{{CWD}}", "Workspace 绝对路径"], + [ + "{{PROJECT_DIR}}", + "PenguinHarness 应用数据根目录(存放全部 Agent 数据与 Project 级数据;不是本次任务的工作目录)", + ], ["{{AGENT_ID}}", "当前 Agent id"], - ["{{PROJECT_DIR}}", "Project 目录绝对路径(Agent State/scratchpad 由此拼出)"], + ["{{CWD}}", "Workspace 绝对路径"], + ["{{PROVIDER}}", "模型 provider 分组"], + ["{{MODEL_ID}}", "上游模型 id"], ["{{SESSION_ID}}", "当前 Session id"], ] as ReadonlyArray, maxTurns: "max_turns(单 Task 最大轮次,-1 不限制)", @@ -242,6 +248,13 @@ export const zh = { importDone: (v: number): string => `导入完成,Agent State 版本 v${v}`, importConflictTitle: "版本冲突", importConflictBody: "快照包版本不高于当前版本,导入将覆盖现有 Agent State。确认继续?", + resetConfigTitle: "还原为默认配置", + resetConfigDesc: + "把 system_config.yaml 还原为当前内置默认值(与 Skill 更新同语义):自定义的系统提示词、工具列表、模型/压缩参数与 MCP Server 将被覆盖,仅保留名称、描述与版本号。", + resetConfigAction: "还原为默认配置", + resetConfigConfirmBody: + "此操作会用当前默认值覆盖该 Agent 的现有配置:自定义系统提示词、工具列表、模型/压缩参数与 MCP Server 全部被替换,仅保留名称与描述。与 Skill 更新一样不可撤销,确认继续?", + resetConfigDone: "配置已还原为当前默认值", }, models: {