From 6710527e8f7150a6c4997d566a380454e33f455e Mon Sep 17 00:00:00 2001 From: brother7 <7brother7@gmail.com> Date: Sat, 5 Sep 2026 12:34:36 +0800 Subject: [PATCH] feat: make project scaffold project-wide --- .../20260905-project-plugin-scope-7c4e9a21.md | 116 ++++++++++++++++++ electron/coding-plugins/effective-resolver.ts | 17 ++- shared/coding-plugins.ts | 9 ++ src/pages/Plugins/PluginDetails.tsx | 9 +- src/pages/Plugins/index.tsx | 15 ++- src/pages/Plugins/plugin-workspace-model.ts | 11 +- .../coding-plugin-effective-resolver.test.ts | 77 ++++++++++++ tests/unit/plugin-workspace-model.test.ts | 4 +- tests/unit/plugins-page.test.tsx | 6 +- 9 files changed, 252 insertions(+), 12 deletions(-) create mode 100644 .project-docs/30-worklog/tasks/20260905-project-plugin-scope-7c4e9a21.md diff --git a/.project-docs/30-worklog/tasks/20260905-project-plugin-scope-7c4e9a21.md b/.project-docs/30-worklog/tasks/20260905-project-plugin-scope-7c4e9a21.md new file mode 100644 index 0000000..30e2395 --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260905-project-plugin-scope-7c4e9a21.md @@ -0,0 +1,116 @@ +# Task: Make project-scoped Plugins active without partner assignment + +## Identity + +- Task ID: 20260905-project-plugin-scope-7c4e9a21 +- Mode: Feature +- Branch: codex/20260905-project-plugin-scope-7c4e9a21-project-plugin-scope +- Worktree: D:\Datas\OthersProjects\.codex-worktrees\makelore\20260905-project-plugin-scope-7c4e9a21 +- Base commit: d642d7607c26dee01ef65b4e70dd756465dea16a +- Owner: codex-root +- Status: Ready for Integration + +## Scope + +- Reproduce whether the code-owned bundled `makelore.project-scaffold` Skill is + currently excluded from a parent Agent when the Plugin is acquired and enabled for + the project but not explicitly assigned to that Agent. +- Change only Project Scaffold activation semantics so project enablement is sufficient + for parent-Agent materialization; preserve Account Library acquisition, child-worker + emptiness, frozen worker generations, and every other Plugin's assignment behavior. +- Remove Project Scaffold's partner-assignment action/status from the unified Plugins + workspace and add focused runtime plus Renderer regressions. +- Record the accepted user decision as an Integration promotion candidate; this Feature + task does not edit canonical architecture, domain, or ADR files. + +## Intent And Constraints + +- Concurrent Task Gate: Passed. The exact owner/worktree/branch/base identity was + verified through `task_context.py status --json`; related Plugin download/status + tasks are Ready for Integration and have no active writer overlapping this scope. +- Planning Gate: Passed after loading the project entry docs, current state, decision + index, ADR-008, architecture/data flow/module map, business rules, success criteria, + glossary, evidence, reflection, commitments, stale items, and relevant peer records. +- Project Context Loaded: + - Positioning: MakeLore Code is a Main-owned Pi runtime whose Plugin state is projected + through existing Marketplace/project/Agent authorities rather than Renderer-owned + lifecycle state. + - Current focus: official bundled Project Scaffold delivery and unified `/plugins` + presentation are already integrated; the remaining question is activation scope. + - Applicable decision: ADR-008 previously required acquisition, project enablement, + and Agent assignment. The user's explicit 2026-09-05 decision supersedes only the + assignment requirement for Project Scaffold. + - Architecture boundaries: effective resolution remains Main-owned; Renderer only + projects actions. Parent workers may receive the Skill, child workers remain empty, + and active workers keep frozen resources until replacement/settlement. + - Current state: `makelore.project-scaffold` ships in the signed client and has no + device download path; Account acquisition and project enablement remain distinct. + - Known risk: hiding the UI assignment action without changing the effective resolver + would leave the Skill unusable; broadening all Plugin Skills would silently alter + unrelated assignment semantics. + - Relevant commitment: final packaged Project Scaffold activation still requires a + rebuilt client and installed-client smoke; workspace tests do not prove that step. + - Relevant peers: the bundled-status integration and device-action-gap tasks only + correct delivery presentation/package evidence; neither changes activation scope. +- TDD boundary: first assert the public effective-resolver and Plugins-page behavior at + acquired + project-enabled + unassigned state, then implement the narrowest shared + activation-scope rule that makes those assertions pass. +- Do not modify the occupied client root, Server, Marketplace contracts, Package Store, + billing, hosted execution, local Device Packages, or unrelated Plugin assignments. + +## Outcome + +- Confirmed the defect at the Main-owned effective-resolver seam: an acquired, + project-enabled Project Scaffold Plugin with no Agent assignment was rejected as + `skill_unassigned`, so removing only the Renderer action would not have activated it. +- Added one narrow code-owned project-wide activation invariant for + `makelore.project-scaffold`. Its full Skill set now materializes for every parent Agent + after Account acquisition and project enablement; disabled projects remain blocked and + child Agents remain empty. +- Updated the unified Plugins workspace so Project Scaffold no longer offers partner + assignment. Cards and details now describe its scope as `随项目启用` / `生效范围`. +- Preserved existing assignment behavior for Game Resource, Data Service, Marketplace, + and local Plugins. No manifest schema, server contract, Package Store, billing, or + worker-lifecycle authority was added. + +## Verification + +- TDD RED: the focused resolver/model/page slice produced 3 expected failures and 42 + passes: Project Scaffold returned `skill_unassigned`, the assignment command remained, + and the project-wide scope copy was absent. +- Focused GREEN: 3 files / 45 tests passed. +- Adjacent Plugin/runtime regression: 12 files / 87 tests passed, including effective + resolution, composition, lifecycle, manifest/routes, Pi resource/worker opening, + Project Scaffold, and unified Plugins projection/controller/query/page behavior. +- `corepack pnpm run typecheck`: passed. +- Scoped ESLint: passed. Full `corepack pnpm run lint:check`: 0 errors and 5 unchanged + warnings in untouched Home/Makelore files. +- Full regular unit suite: 225 files passed and 1 unrelated real-process timing test + failed (`pi-agent-server-process-real`, 2630 ms against a 2000 ms threshold); 1888 tests + passed and 2 skipped. The exact failed file then passed in isolation, 6/6. +- Isolated pressure suite: 1/1 passed. +- `corepack pnpm run build:vite`: Renderer, Main, Preload, and utility builds passed with + existing Browserslist/import/chunk warnings. +- `git diff --check`: passed. + +## Follow-ups + +- The Integration task must promote the Project Scaffold activation rule into ADR-008, + module map, data flow, and business rules, then rebuild/install the client and smoke-test + an acquired + project-enabled + unassigned parent Agent. Workspace tests do not prove + the installed-client generation was replaced. + +## Promotion Candidates + +- Update ADR-008, module map, data flow, and business rules during Integration so + `makelore.project-scaffold` is documented as account-acquired + project-enabled and + automatically available to parent Agents, with no partner-assignment state. + - Evidence: focused resolver RED reproduced `skill_unassigned`; the corrected focused + and adjacent suites pass while Game Resource retains `open_agent_assignment`. + - Future impact: Plugin activation-scope changes must update the code-owned scope + predicate and both Main/Renderer regressions; Project Scaffold assignments already + stored in project data become inert but may remain preserved. + - Semantic conflict: supersedes only ADR-008's assignment requirement for Project + Scaffold, not the general assignment model for other Plugins. + - Human confirmation required: No; the user explicitly chose project-level activation + on 2026-09-05. diff --git a/electron/coding-plugins/effective-resolver.ts b/electron/coding-plugins/effective-resolver.ts index 24c96ef..51d3424 100644 --- a/electron/coding-plugins/effective-resolver.ts +++ b/electron/coding-plugins/effective-resolver.ts @@ -3,6 +3,7 @@ import type { CodingPluginDefinition, CodingPluginToolDefinition, } from '../../shared/coding-plugins'; +import { isProjectWideCodingPluginId } from '../../shared/coding-plugins'; import { CORE_CODING_SKILL_IDS, type CodingSkillId, @@ -240,6 +241,15 @@ function marketplaceSkillConflicts(records: readonly DefinitionRecord[]): Readon return blocked; } +function selectedSkillsForDefinition( + definition: CodingPluginDefinition, + assignedSkillIds: readonly string[], +): CodingPluginDefinition['skills'] { + return isProjectWideCodingPluginId(definition.id) + ? definition.skills + : definition.skills.filter(({ id }) => assignedSkillIds.includes(id)); +} + /** * Resolve a worker snapshot from separated Marketplace, Package Store, * project, assignment, and policy state. This module deliberately performs @@ -303,7 +313,7 @@ export class EffectivePluginResolver { const enabled = new Set(await this.enabledPluginIds(input.projectPath)); const requiresLibrary = userDefinitions.some(({ definition, installed }) => ( installed && enabled.has(definition.id) - && definition.skills.some(({ id }) => assigned.includes(id)) + && selectedSkillsForDefinition(definition, assigned).length > 0 )); const library = input.library !== undefined ? input.library @@ -314,7 +324,8 @@ export class EffectivePluginResolver { const serverDefinitions = definitions.filter(({ definition }) => definition.requiresBackend); const needsPolicyRefresh = policyState.status !== 'current' && serverDefinitions.some(({ definition, installed }) => ( - installed && enabled.has(definition.id) && definition.skills.some(({ id }) => assigned.includes(id)) + installed && enabled.has(definition.id) + && selectedSkillsForDefinition(definition, assigned).length > 0 )); if (needsPolicyRefresh && this.options.policyClient) { await this.options.policyClient.refresh(); @@ -330,7 +341,7 @@ export class EffectivePluginResolver { )); continue; } - const selectedSkills = definition.skills.filter(({ id }) => assigned.includes(id)); + const selectedSkills = selectedSkillsForDefinition(definition, assigned); if (selectedSkills.length === 0) { unavailableReasons.push(unavailable(definition.id, 'skill_unassigned', 'Plugin Skill is not assigned')); continue; diff --git a/shared/coding-plugins.ts b/shared/coding-plugins.ts index 6df0a59..7b0b7e4 100644 --- a/shared/coding-plugins.ts +++ b/shared/coding-plugins.ts @@ -106,6 +106,15 @@ export const GAME_RESOURCE_BUNDLED_RELEASE_ID = '00000000-0000-4000-8000-0000000 export const PROJECT_SCAFFOLD_PLUGIN_ID = 'makelore.project-scaffold' as const; export const PROJECT_SCAFFOLD_BUNDLED_RELEASE_ID = '00000000-0000-4000-8000-000000000303' as const; +/** + * Project-wide Plugins materialize all of their Skills for every parent Agent + * once the Plugin is enabled for that project. They never require a separate + * Agent Skill assignment. + */ +export function isProjectWideCodingPluginId(pluginId: string): boolean { + return pluginId === PROJECT_SCAFFOLD_PLUGIN_ID; +} + export const CODE_OWNED_OPTIONAL_BUNDLED_RELEASES = Object.freeze({ [GAME_RESOURCE_PLUGIN_ID]: Object.freeze({ releaseId: GAME_RESOURCE_BUNDLED_RELEASE_ID, diff --git a/src/pages/Plugins/PluginDetails.tsx b/src/pages/Plugins/PluginDetails.tsx index 477d2a8..054e7f2 100644 --- a/src/pages/Plugins/PluginDetails.tsx +++ b/src/pages/Plugins/PluginDetails.tsx @@ -14,6 +14,7 @@ import { import type { DataServiceInstanceState } from '../../../shared/data-service'; import { isBundledOfficialPlugin, + isProjectWideOfficialPlugin, type PluginWorkspaceCommand, type PluginWorkspaceItem, } from './plugin-workspace-model'; @@ -402,8 +403,12 @@ export function PluginDetails(props: PluginDetailsProps) {
-

伙伴分配

- {props.item.source === 'local' +

+ {isProjectWideOfficialPlugin(props.item) ? '生效范围' : '伙伴分配'} +

+ {isProjectWideOfficialPlugin(props.item) + ?

启用当前项目后,插件会自动提供给该项目的主伙伴,无需单独分配;子伙伴不会继承。

+ : props.item.source === 'local' ?

本机包全局生效,不作为项目 Agent Skill 分配项;符合条件的新建或空闲主伙伴及其 worker 会按既有生命周期加载。

: props.item.assignedAgentNames.length ?
{props.item.assignedAgentNames.map((name) => {name})}
diff --git a/src/pages/Plugins/index.tsx b/src/pages/Plugins/index.tsx index ec742fc..1b7eccc 100644 --- a/src/pages/Plugins/index.tsx +++ b/src/pages/Plugins/index.tsx @@ -21,7 +21,11 @@ import type { PluginWorkspaceProjection, PluginWorkspaceState, } from './plugin-workspace-model'; -import { buildPluginWorkspaceProjection, isBundledOfficialPlugin } from './plugin-workspace-model'; +import { + buildPluginWorkspaceProjection, + isBundledOfficialPlugin, + isProjectWideOfficialPlugin, +} from './plugin-workspace-model'; import { resolvePluginWorkspaceSearch, serializePluginWorkspaceSearch } from './plugin-workspace-query'; const DELIVERY_TEXT = { @@ -82,10 +86,15 @@ function cardSource(item: PluginWorkspaceItem): string { function agentText(item: PluginWorkspaceItem): string { if (item.source === 'local') return '本机全局生效'; + if (isProjectWideOfficialPlugin(item)) return '随项目启用'; const count = item.assignedAgentIds.length; return count ? `已分配 ${count} 位伙伴` : '尚未分配伙伴'; } +function agentLabel(item: PluginWorkspaceItem): string { + return isProjectWideOfficialPlugin(item) ? '生效范围' : '伙伴'; +} + function PluginCard({ item, grouped = false, @@ -112,7 +121,7 @@ function PluginCard({
当前项目
{PROJECT_TEXT[item.projectState]}
本机状态
{item.source === 'local' ? (item.localEnabled ? '本机全局已启用' : '本机全局已停用') : item.official?.installation?.version ? `官方包 ${item.official.installation.version}` : isBundledOfficialPlugin(item) ? '随应用提供' : '未下载官方包'}
-
伙伴
{agentText(item)}
+
{agentLabel(item)}
{agentText(item)}
计费
{BILLING_TEXT[item.billing]}
{item.deviceReason ? ( @@ -182,7 +191,7 @@ export function PluginsView(props: PluginsViewProps) {

MakeLore Code

插件

- MakeLore 运营发布官方插件;本机 Skill 和 Pi extension 仅通过对话安装。获取、项目启用与伙伴分配彼此独立,本页集中展示账号、设备、当前项目与伙伴状态。 + MakeLore 运营发布官方插件;本机 Skill 和 Pi extension 仅通过对话安装。获取与项目启用彼此独立;需要定向生效的插件还可分配伙伴。