From c6f3780b80db5ee92644900ca5cd593d85048d9e Mon Sep 17 00:00:00 2001 From: Ziyang Zeng Date: Sun, 8 Nov 2026 15:57:41 +0000 Subject: [PATCH 1/2] feat: complete agent instructions, skills and MCP coverage --- CONTRIBUTING.md | 10 +- docker/entrypoint.sh | 6 +- docker/runtime/patches/agent-resources.mjs | 23 + .../patches/antigravity-user-resources.ts | 127 +++++ .../patches/cursor-global-instructions.ts | 15 + docker/runtime/patches/index.mjs | 4 +- docker/setup/agent-resources.mjs | 19 + docker/setup/app.js | 17 +- docker/setup/client/mcp-editor.js | 4 +- docker/setup/client/mcp-inspector.js | 6 +- docker/setup/client/mcp-model.js | 11 +- docker/setup/client/model.js | 8 +- docker/setup/client/skill-import.js | 2 +- docker/setup/client/skill-inspector.js | 77 ++- docker/setup/client/skill-model.js | 9 +- docker/setup/client/skills.js | 15 +- docker/setup/console.css | 2 +- docker/setup/context.mjs | 46 +- docker/setup/gateways.mjs | 24 +- docker/setup/mcp.mjs | 33 +- docker/setup/mcp/adapters.mjs | 74 ++- docker/setup/mcp/parse.mjs | 29 +- docker/setup/mcp/store.mjs | 15 +- docker/setup/mcp/toml.mjs | 2 +- docker/setup/server.mjs | 3 + docker/setup/skills.mjs | 36 +- docker/setup/skills/adapters.mjs | 18 +- docker/setup/skills/additional-policies.mjs | 100 ++++ docker/setup/skills/files.mjs | 2 +- docker/setup/skills/imports.mjs | 3 +- docker/setup/skills/placement.mjs | 73 +++ docs/context-management.md | 85 +++- docs/mcp-servers.md | 66 ++- .../agent-resources/instructions-dark.png | Bin 0 -> 166349 bytes .../agent-resources/instructions-light.png | Bin 0 -> 181118 bytes docs/media/agent-resources/mcp-dark.png | Bin 0 -> 129052 bytes docs/media/agent-resources/mcp-light.png | Bin 0 -> 165480 bytes docs/media/agent-resources/skills-dark.png | Bin 0 -> 153596 bytes docs/media/agent-resources/skills-light.png | Bin 0 -> 197442 bytes docs/native-runtime-patches.md | 12 + scripts/agent-resources-native-smoke.mjs | 117 +++++ scripts/context-ui-audit.js | 26 +- scripts/fixtures/console/mcp.json | 220 ++++++++- .../fixtures/console/skills-antigravity.json | 8 +- scripts/fixtures/console/skills-cursor.json | 8 +- scripts/fixtures/console/skills-grok.json | 380 ++++++++++----- scripts/fixtures/console/skills-muse.json | 452 +++++++++++++----- scripts/fixtures/console/skills-pi.json | 68 +++ scripts/fixtures/console/skills-reach.json | 23 +- scripts/fixtures/console/status.json | 35 ++ scripts/lib/console-fixtures.cjs | 4 +- scripts/lib/mcp-fixtures.cjs | 2 +- scripts/lib/skills-fixtures.cjs | 27 +- scripts/mcp-native-smoke.mjs | 13 +- scripts/mcp-ui-audit.js | 40 +- scripts/skills-ui-audit.js | 97 +++- scripts/smoke-test.sh | 15 +- tests/agent-resources-patch.test.mjs | 118 +++++ tests/context-api.test.mjs | 2 +- tests/context.test.mjs | 31 +- tests/credentials-usage.test.mjs | 10 +- tests/full-agent-resources.test.mjs | 263 ++++++++++ tests/gateways-service.test.mjs | 49 +- tests/mcp-api.test.mjs | 6 +- tests/mcp-model.test.mjs | 25 +- tests/mcp-regressions.test.mjs | 3 +- tests/mcp.test.mjs | 7 +- tests/persist-credentials.test.sh | 26 + tests/skill-adapters.test.mjs | 8 +- tests/skill-files.test.mjs | 2 +- tests/skill-imports.test.mjs | 4 +- tests/skill-library.test.mjs | 2 +- tests/skill-reach.test.mjs | 2 +- tests/skill-update-review.test.mjs | 6 +- 74 files changed, 2562 insertions(+), 513 deletions(-) create mode 100644 docker/runtime/patches/agent-resources.mjs create mode 100644 docker/runtime/patches/antigravity-user-resources.ts create mode 100644 docker/runtime/patches/cursor-global-instructions.ts create mode 100644 docker/setup/agent-resources.mjs create mode 100644 docker/setup/skills/additional-policies.mjs create mode 100644 docker/setup/skills/placement.mjs create mode 100644 docs/media/agent-resources/instructions-dark.png create mode 100644 docs/media/agent-resources/instructions-light.png create mode 100644 docs/media/agent-resources/mcp-dark.png create mode 100644 docs/media/agent-resources/mcp-light.png create mode 100644 docs/media/agent-resources/skills-dark.png create mode 100644 docs/media/agent-resources/skills-light.png create mode 100644 scripts/agent-resources-native-smoke.mjs create mode 100644 scripts/fixtures/console/skills-pi.json create mode 100644 tests/agent-resources-patch.test.mjs create mode 100644 tests/full-agent-resources.test.mjs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 94abe247..3a9e542a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -78,7 +78,7 @@ Issues and pull requests are welcome. Before opening one: API. It never connects to production or executes source files. Reload reads the current console assets; restart resets fixture mutations. This is a review server, not a deployment mode. `--scenarios skills --skill-overlays` adds the - seven-provider library, policy menu, inspector and all three import steps to + eight-provider library, policy menu, inspector and all three import steps to `console-screens.js`. Fixtures use synthetic instruction text and paths. `node scripts/skills-ui-audit.js http://127.0.0.1:23785/__setup/ dev` exercises desktop, phone, tablet and embedded flows in both themes, including settings @@ -148,7 +148,13 @@ Issues and pull requests are welcome. Before opening one: For shared MCP changes, run `node scripts/mcp-native-smoke.mjs PROVIDER /absolute/path/to/executable` for Claude, Codex, OpenCode (both V1 and V2) and -Cursor. It uses disposable homes and fixture servers without model requests. +Cursor, Grok, Muse and Pi. It uses disposable homes and fixture servers without +external model requests. Muse runs its local echo provider. Also run +`node scripts/agent-resources-native-smoke.mjs PROVIDER /absolute/executable` +for Grok, Muse and Pi to verify instruction/skill loading and native skill +settings. Antigravity's private-profile normalization and Cursor's instruction +workspace are covered by `tests/agent-resources-patch.test.mjs`; changes must +also build against the current Stable and Nightly source anchors. `node scripts/mcp-ui-audit.js` checks the MCP page’s matrix, per-agent Off/On, that the agent list can be reached at every size, snippet import, masked values, edit/update, sharing consent, partial removal, revision conflicts and failed/late reads in both themes at phone, desktop diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index afe8dcbf..f61d70db 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -297,11 +297,11 @@ register_projects # everything there already survives a recreate. Shared skills stay # discoverable through ~/.agents/skills natively. ensure_opencode_config() # below owns that layout. -AGENT_DIRS=".claude .codex .cursor .grok .pi .local/share/opencode .config/muse .local/share/muse .agents/skills .local/state/skills .gemini/config/skills .gemini/antigravity-cli/skills" +AGENT_DIRS=".claude .codex .cursor .grok .pi .local/share/opencode .config/muse .local/share/muse .agents/skills .local/state/skills .gemini/config/skills .gemini/config .gemini/antigravity-cli/skills" AGENT_DIRS="$AGENT_DIRS .config/gh .config/glab-cli .config/tea .local/share/forgejo-cli .azure" # And git's own settings, plus Claude's global account/MCP config outside its # config directory. Git and Claude's native writer preserve these file links. -AGENT_FILES=".gitconfig .agents/.skill-lock.json .claude.json" +AGENT_FILES=".gitconfig .agents/.skill-lock.json .claude.json .gemini/AGENTS.md .gemini/GEMINI.md" persist_agent_credentials() { [ "$T3_PERSIST_AGENT_CREDENTIALS" = "1" ] || return 0 @@ -341,7 +341,7 @@ persist_agent_credentials() { if [ "$rel" = ".claude.json" ]; then [ -e "$dst" ] || printf '{}\n' > "$dst" chmod 600 "$dst" - elif [ "$rel" != ".agents/.skill-lock.json" ]; then + elif [ "$rel" != ".agents/.skill-lock.json" ] && [ "$rel" != ".gemini/AGENTS.md" ] && [ "$rel" != ".gemini/GEMINI.md" ]; then [ -e "$dst" ] || : > "$dst" fi ;; diff --git a/docker/runtime/patches/agent-resources.mjs b/docker/runtime/patches/agent-resources.mjs new file mode 100644 index 00000000..0d6df4bd --- /dev/null +++ b/docker/runtime/patches/agent-resources.mjs @@ -0,0 +1,23 @@ +import { readFileSync } from 'node:fs'; +export const files = ['apps/server/src/provider/antigravityAuthSupport.ts', 'packages/provider-cursor/src/server/driver.ts']; +export const creates = ['apps/server/src/provider/antigravityUserResources.ts', 'packages/provider-cursor/src/server/globalInstructions.ts']; +export const assets = [new URL('./antigravity-user-resources.ts', import.meta.url), new URL('./cursor-global-instructions.ts', import.meta.url)]; +export function patchAgentResources(source) { + const next = { ...source }; + for (let i = 0; i < creates.length; i++) { + if (Object.hasOwn(next, creates[i])) throw new Error('An agent resource bridge already exists upstream. Review it before publishing.'); + next[creates[i]] = readFileSync(assets[i], 'utf8'); + } + const change = (file, before, after) => { + if (typeof next[file] !== 'string' || next[file].split(before).length !== 2) throw new Error(`The agent-resources patch no longer matches ${file}. Review the upstream source before publishing this runtime.`); + next[file] = next[file].replace(before, after); + }; + change(files[0], 'import * as NodeFSP from "node:fs/promises";', 'import * as NodeFSP from "node:fs/promises";\nimport { linkAntigravityUserResources } from "./antigravityUserResources.ts";'); + change(files[0], ' yield* linkAntigravityUserSkills({ profileDirectory: geminiHome, userHome, platform });', ` yield* linkAntigravityUserSkills({ profileDirectory: geminiHome, userHome, platform }); + yield* Effect.tryPromise(() => linkAntigravityUserResources(geminiHome, userHome)).pipe( + Effect.mapError((error) => authSupportError(error instanceof Error ? error.message : "Antigravity global instructions and MCP settings could not be linked into its T3 profile.")), + );`); + change(files[1], 'import * as CursorAgentSdk from "./CursorAgentSdk.ts";', 'import * as CursorAgentSdk from "./CursorAgentSdk.ts";\nimport { withCursorGlobalInstructions } from "./globalInstructions.ts";'); + change(files[1], '.open({ ...input, options: { ...input.options, apiKey } })', '.open({ ...input, options: withCursorGlobalInstructions({ ...input.options, apiKey }, processEnv) })'); + return next; +} diff --git a/docker/runtime/patches/antigravity-user-resources.ts b/docker/runtime/patches/antigravity-user-resources.ts new file mode 100644 index 00000000..d93b5066 --- /dev/null +++ b/docker/runtime/patches/antigravity-user-resources.ts @@ -0,0 +1,127 @@ +// Only context resources cross into T3 profiles. Authentication, hooks and +// native settings remain private to each Antigravity instance. +import * as fs from "node:fs/promises"; +import path from "node:path"; +import { createHash, randomBytes } from "node:crypto"; + +const pending = new Map>(); +export async function linkAntigravityUserResources(profile: string, userHome: string) { + const key = path.resolve(profile), previous = pending.get(key) || Promise.resolve(); + const next = previous.catch(() => {}).then(() => syncUserResources(key, userHome)); + pending.set(key, next); + try { await next; } finally { if (pending.get(key) === next) pending.delete(key); } +} +const conflict = (file: string) => new Error(`Antigravity has private settings at ${file}. Move or merge that file before using global resources in this T3 profile.`); +async function syncUserResources(profile: string, userHome: string) { + const origin = path.join(userHome, ".gemini"); + for (const relative of ["AGENTS.md", "GEMINI.md", "config/AGENTS.md", "config/GEMINI.md", "config/rules"]) { + const target = path.join(origin, relative), link = path.join(profile, relative); + if (target === link) continue; + const source = await fs.stat(target).catch((error: NodeJS.ErrnoException) => { if (error.code !== "ENOENT") throw error; return null; }); + const existing = await fs.lstat(link).catch((error: NodeJS.ErrnoException) => { if (error.code !== "ENOENT") throw error; return null; }); + if (existing?.isSymbolicLink()) { + const current = path.resolve(path.dirname(link), await fs.readlink(link)); + // Preserve foreign links. Only this bridge's exact target is ours. + if (current !== target) { if (source) throw conflict(link); continue; } + if (source) continue; + await fs.unlink(link); + } else if (existing) { + // Empty native placeholders may be removed; never replace user content. + if (!source) continue; + if (!existing.isDirectory() || !source.isDirectory()) throw conflict(link); + try { await fs.rmdir(link); } catch (error) { + if (["ENOTEMPTY", "EEXIST"].includes((error as NodeJS.ErrnoException).code || "")) throw conflict(link); + throw error; + } + } + if (!source) continue; + await fs.mkdir(path.dirname(link), { recursive: true }); + try { await fs.symlink(target, link, source.isDirectory() ? "dir" : "file"); } + catch (error) { if ((error as NodeJS.ErrnoException).code === "EEXIST") throw conflict(link); throw error; } + } + await syncAntigravityUserMcp(profile, userHome); +} + +const fingerprint = (text: string) => createHash("sha256").update(text).digest("hex"); +const object = (value: unknown): value is Record => typeof value === "object" && value !== null && !Array.isArray(value); +async function syncAntigravityUserMcp(profile: string, userHome: string) { + const source = path.join(userHome, ".gemini/config/mcp_config.json"), file = path.join(profile, "config/mcp_config.json"); + if (source === file) return; + const stat = await fs.stat(source).catch((error: NodeJS.ErrnoException) => { if (error.code !== "ENOENT") throw error; return null; }); + const existing = await fs.lstat(file).catch((error: NodeJS.ErrnoException) => { if (error.code !== "ENOENT") throw error; return null; }); + let owned: string | null = null, priorLink: string | null = null; + if (existing) { + // A content-addressed snapshot identifies bridge ownership without a + // separate marker that could go stale after an interrupted write. + if (existing.isSymbolicLink()) { + priorLink = await fs.readlink(file); + const match = /^\.t3-user-mcp-([a-f0-9]{64})\.json$/.exec(priorLink); + if (match) { + const snapshot = path.join(path.dirname(file), priorLink); + const info = await fs.lstat(snapshot).catch((error: NodeJS.ErrnoException) => { if (error.code !== "ENOENT") throw error; return null; }); + if (info?.isFile() && info.size <= 1024 * 1024 && fingerprint(await fs.readFile(snapshot, "utf8")) === match[1]) owned = snapshot; + } + } + if (!owned) { if (stat) throw conflict(file); return; } + } + const unchanged = async () => { + const current = await fs.lstat(file).catch((error: NodeJS.ErrnoException) => { if (error.code !== "ENOENT") throw error; return null; }); + if (!existing) { if (current) throw conflict(file); return; } + if (!current?.isSymbolicLink() || current.ino !== existing.ino || current.dev !== existing.dev || await fs.readlink(file) !== priorLink) throw conflict(file); + if (owned && fingerprint(await fs.readFile(owned, "utf8")) !== path.basename(owned).slice(13, -5)) throw conflict(file); + }; + if (!stat) { + await unchanged(); + if (owned) { await fs.unlink(file); await fs.unlink(owned); } + return; + } + if (!stat.isFile() || stat.size > 1024 * 1024) throw new Error("Antigravity MCP configuration must be a regular file within 1 MiB."); + const handle = await fs.open(source, "r"); + let text: string; + try { + const buffer = Buffer.alloc(1024 * 1024 + 1); + let bytes = 0; + while (bytes < buffer.length) { + const result = await handle.read(buffer, bytes, buffer.length - bytes, bytes); + if (!result.bytesRead) break; + bytes += result.bytesRead; + } + if (bytes > 1024 * 1024) throw new Error("Antigravity MCP configuration exceeds 1 MiB."); + text = buffer.toString("utf8", 0, bytes); + } finally { await handle.close(); } + let value: unknown; + try { value = JSON.parse(text); } catch { throw new Error("Antigravity global MCP configuration is invalid JSON."); } + if (!object(value)) throw new Error("Antigravity global MCP configuration must be an object."); + const entries = value.mcpServers ?? value; + if (!object(entries)) throw new Error("Antigravity mcpServers must be an object."); + if (Object.values(entries).some(entry => !object(entry))) throw new Error("Antigravity MCP server entries must be objects."); + const servers = Object.fromEntries(Object.entries(entries).filter(([, entry]) => (entry as Record).disabled !== true).map(([name, entry]) => { + const native = entry as Record; + // Older ACP releases only recognize url and do not implement disabled. + return [name, { ...native, ...(typeof native.serverUrl === "string" ? { url: native.serverUrl } : {}) }]; + })); + const next = JSON.stringify({ mcpServers: servers }, null, 2) + "\n"; + await fs.mkdir(path.dirname(file), { recursive: true }); + const snapshot = path.join(path.dirname(file), `.t3-user-mcp-${fingerprint(next)}.json`); + if (owned === snapshot) return; + let created = false; + try { await fs.writeFile(snapshot, next, { mode: 0o600, flag: "wx" }); created = true; } + catch (error) { + if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error; + const info = await fs.lstat(snapshot); + if (!info.isFile() || info.size > 1024 * 1024 || await fs.readFile(snapshot, "utf8") !== next) throw conflict(snapshot); + } + const temporary = file + ".tmp." + randomBytes(8).toString("hex"); + let published = false; + try { + await fs.symlink(path.basename(snapshot), temporary); + await unchanged(); + if (existing) await fs.rename(temporary, file); + else await fs.link(temporary, file); + published = true; + if (owned) await fs.unlink(owned); + } finally { + await fs.rm(temporary, { force: true }); + if (created && !published) await fs.rm(snapshot, { force: true }); + } +} diff --git a/docker/runtime/patches/cursor-global-instructions.ts b/docker/runtime/patches/cursor-global-instructions.ts new file mode 100644 index 00000000..a13917f6 --- /dev/null +++ b/docker/runtime/patches/cursor-global-instructions.ts @@ -0,0 +1,15 @@ +// The SDK loads rules from each local workspace. A dedicated additional +// workspace supplies global guidance without modifying project files or +// replacing Cursor's built-in system prompt. +import type { AgentOptions } from "@cursor/sdk"; +import { existsSync } from "node:fs"; +import path from "node:path"; +import { homedir } from "node:os"; + +export function withCursorGlobalInstructions(options: AgentOptions, env: NodeJS.ProcessEnv): AgentOptions { + const home = env.HOME || homedir(); + const root = path.join(home, ".cursor"); + const directory = path.join(root, "t3-instructions"); + if (!options.local || !existsSync(path.join(directory, "AGENTS.md"))) return options; + return { ...options, local: { ...options.local, dirs: [...new Set([...(options.local.dirs || []), directory])] } }; +} diff --git a/docker/runtime/patches/index.mjs b/docker/runtime/patches/index.mjs index 038bcfc7..9851f592 100644 --- a/docker/runtime/patches/index.mjs +++ b/docker/runtime/patches/index.mjs @@ -1,3 +1,4 @@ +import * as agentResources from "./agent-resources.mjs"; import * as opencodeSkills from "./opencode-skills.mjs"; import * as projectDirectory from "./project-directory.mjs"; import * as setupPalette from "./setup-palette.mjs"; @@ -6,7 +7,8 @@ import * as gatewayQuota from "./gateway-quota.mjs"; // Included in the registry fingerprint. Bump when the build contract changes. export const BUILD_REVISION = 4; -export const patches = [{ name: "project-directory", module: new URL("./project-directory.mjs", import.meta.url), files: projectDirectory.files, apply: projectDirectory.patchProjectDirectory }, +export const patches = [{ name: "agent-resources", module: new URL("./agent-resources.mjs", import.meta.url), files: agentResources.files, creates: agentResources.creates, assets: agentResources.assets, apply: agentResources.patchAgentResources }, + { name: "project-directory", module: new URL("./project-directory.mjs", import.meta.url), files: projectDirectory.files, apply: projectDirectory.patchProjectDirectory }, { name: "gateway-capabilities", module: new URL("./gateway-capabilities.mjs", import.meta.url), files: gatewayCapabilities.files, apply: gatewayCapabilities.patchGatewayCapabilities }, { name: "gateway-quota", module: new URL("./gateway-quota.mjs", import.meta.url), files: gatewayQuota.files, creates: gatewayQuota.creates, assets: gatewayQuota.assets, apply: gatewayQuota.patchGatewayQuota }, { name: "opencode-skills", module: new URL("./opencode-skills.mjs", import.meta.url), files: opencodeSkills.files, apply: opencodeSkills.patchOpenCodeSkills }, diff --git a/docker/setup/agent-resources.mjs b/docker/setup/agent-resources.mjs new file mode 100644 index 00000000..5c9f8295 --- /dev/null +++ b/docker/setup/agent-resources.mjs @@ -0,0 +1,19 @@ +// Resource paths shared by Instructions, Skills and MCP. Explicit homes keep +// every operation testable without reading the operator's resources. +import path from 'node:path'; +import { opencodeConfigRoot } from './opencode-config.mjs'; + +export const AGENT_NAMES = Object.freeze({ + claude: 'Claude Code', codex: 'Codex', opencode: 'OpenCode', cursor: 'Cursor', + grok: 'Grok Build', muse: 'Muse Code', antigravity: 'Antigravity', pi: 'Pi', +}); +export function agentResourcePaths(home = '/home/t3', env = {}) { + const at = (key, fallback) => path.resolve(home, String(env[key] || '').trim() || fallback); + const config = at('XDG_CONFIG_HOME', '.config'); + return { + claude: at('CLAUDE_CONFIG_DIR', '.claude'), codex: at('CODEX_HOME', '.codex'), + opencode: opencodeConfigRoot(home, env), cursor: path.join(home, '.cursor'), + grok: at('GROK_HOME', '.grok'), muse: path.join(config, 'muse'), + antigravity: path.join(home, '.gemini', 'config'), pi: at('PI_CODING_AGENT_DIR', '.pi/agent'), + }; +} diff --git a/docker/setup/app.js b/docker/setup/app.js index 51e25165..0c8a0b21 100644 --- a/docker/setup/app.js +++ b/docker/setup/app.js @@ -1738,7 +1738,7 @@ const shared = M.contextView(state.status).present; const options = [ ['shared', 'Shared', !shared && row.mode !== 'shared'], - ['separate', 'Own ' + row.ownFile, false], + ['separate', 'Own file', false], ['none', 'Nothing', false], ]; return html` @@ -1748,7 +1748,7 @@
${row.name}${badge(row.badge)}
${row.error ? statusLine(row.status) : html`${row.mode === 'shared' ? icon('link') : ''}${T3SkillModel.displayPath(row.path, state.status?.paths?.volume) || row.ownFile}${row.mode === 'shared' ? html`→ shared` : ''}${row.mode === 'separate' && typeof row.bytes === 'number' ? html`${M.formatBytes(row.bytes)}` : ''}${row.mode === 'separate' ? html`` : ''}`} -
${options.map(([mode, label, off]) => html``)}
+
${options.map(([mode, label, off]) => html``)}
`; }; @@ -1771,7 +1771,7 @@ ${rows.length ? html`
${rows.map(contextProviderRow)}
` : html`
No agent that reads global instructions is installed.
`}
${icon('info', 'tc-icon--sm')}Switching away from an own file keeps it as a backup next to it.
-

Cursor, Grok Build, Muse Code and Antigravity global instructions aren’t managed here. A repository’s own AGENTS.md still applies on top of these.

Agents read instructions when a session starts. Start a new one after you save.

`; +

All eight agents can use shared or separate instructions. Cursor loads this file through T3’s SDK integration. Other native rules and repository instructions still apply.

Agents read instructions when a session starts. Start a new one after you save.

`; return html`
${icon('chevron-right', 'tc-back-icon')}Setup

Instructions & skills

${EMBED ? html`` : html`${icon('info')}`}
${pageHead('instructions')} @@ -1800,8 +1800,9 @@ const openContextEditor = async (trigger, provider = null) => { if (contextEditor.layer) return; Object.assign(contextEditor, { content: '', original: '', loading: true, saving: false, error: null, provider, revision: null, path: '', readError: false }); - const providerName = { claude: 'Claude Code', codex: 'Codex', opencode: 'OpenCode' }[provider]; - const filename = provider === 'claude' ? 'CLAUDE.md' : 'AGENTS.md'; + const providerRow = provider ? M.contextProviderRows(state.status).find(row => row.id === provider) : null; + const providerName = providerRow?.name || provider; + const filename = providerRow?.ownFile || 'AGENTS.md'; const title = provider ? providerName + '’s ' + filename : 'Shared AGENTS.md'; const readers = M.contextAgentsMdRow(state.status).readers; const isDirty = () => !contextEditor.readError && contextEditor.content !== contextEditor.original; @@ -4142,17 +4143,17 @@ } if (a.mode === 'none') { const confirmed = await Kit.confirm({ - title: row.name + ': read no instructions?', + title: row.name + ': remove these global instructions?', body: row.mode === 'separate' ? 'Its own ' + row.ownFile + ' is backed up, and comes back if you choose it again.' : 'It stops reading the shared AGENTS.md. A file of its own backed up earlier is kept for later.', - confirm: 'Read nothing', + confirm: 'Disconnect instructions', }); if (!confirmed) return; } const result = await api('/context/agents-md', { body: { provider: a.id, mode: a.mode, sourceRevision: row.sourceRevision } }); if (!result.ok) { Kit.toast('Couldn’t change what ' + row.name + ' reads', { tone: 'danger', detail: result.error }); return; } - Kit.toast(row.name + (a.mode === 'shared' ? ' reads the shared AGENTS.md' : a.mode === 'none' ? ' reads no instructions' : ' reads its own ' + row.ownFile)); + Kit.toast(row.name + (a.mode === 'shared' ? ' reads the shared AGENTS.md' : a.mode === 'none' ? ' has no managed global instructions' : ' reads its own ' + row.ownFile)); await loadStatus(); if (a.mode === 'separate') openContextEditor(el, a.id); } finally { diff --git a/docker/setup/client/mcp-editor.js b/docker/setup/client/mcp-editor.js index 7e2b891f..6b58eae3 100644 --- a/docker/setup/client/mcp-editor.js +++ b/docker/setup/client/mcp-editor.js @@ -158,7 +158,7 @@ const T3McpEditor = (() => { const label = mode === 'edit' ? (state.kind === 'differs' ? 'Replace in ' : state.kind === 'none' ? 'Add to ' : 'Update ') + p.name : p.name; const note = native?.error || (mode === 'edit' ? state.kind === 'differs' ? 'Its copy differs. Its own changes would be lost.' : state.kind === 'none' ? 'Not added yet' : 'Uses the shared version' + (state.kind === 'off' ? ' · stays Off' : '') : mode === 'share' ? p.id === view.source ? 'Already has this version' : versionDiffers(p.id) ? 'Its version is replaced' : S.has(native, view.name) ? 'Already has this version' : 'Added' : ctx.pathLabel(native?.path)); return html``; - })}${!managed.length ? html`

No managed agents are installed. Save the server now, then add it after installing an agent.

` : ''}

${view.snapshot.providers.find(p => p.provider === 'grok')?.imports?.length ? 'Grok Build may import it from Claude Code or Cursor. ' : ''}Muse Code and Antigravity aren’t managed here yet.

${view.agents.size}${mode === 'edit' ? 'agents update' : mode === 'share' ? 'agents use ' + view.name : 'settings files change'}

${mode === 'edit' ? 'Agents you leave unchecked keep their current copy and show Differs until you update them.' : mode === 'share' ? view.name + ' moves to Shared servers. ' + S.nameOf(view.source) + '’s entry stays exactly as it is.' : 'Only the ' + (view.draft.name || 'named') + ' entry is written. Other servers and settings in those files stay as they are, and a backup of each is kept.'}

${icon('info', 'tc-icon--sm')}${railHint()}

`; + })}${!managed.length ? html`

No managed agents are installed. Save the server now, then add it after installing an agent.

` : ''}

Each selected agent uses its native settings. Start a new session after saving.

${view.agents.size}${mode === 'edit' ? 'agents update' : mode === 'share' ? 'agents use ' + view.name : 'settings files change'}

${mode === 'edit' ? 'Agents you leave unchecked keep their current copy and show Differs until you update them.' : mode === 'share' ? view.name + ' moves to Shared servers. ' + S.nameOf(view.source) + '’s entry stays exactly as it is.' : 'Only the ' + (view.draft.name || 'named') + ' entry is written. Other servers and settings in those files stay as they are, and a backup of each is kept.'}

${icon('info', 'tc-icon--sm')}${railHint()}

`; const resultBody = () => html`
${icon('info')}
Shared server ${mode === 'share' ? 'shared' : 'saved'}${M.plural(view.results.filter(r => r.ok).length, 'agent')} updated. Other entries stay as they are.
${view.results.map(result => html`
${ctx.mark(result.provider)}
${S.nameOf(result.provider)}${result.ok ? 'Saved' : result.error}
${icon(result.ok ? 'circle-check' : 'triangle-alert')}
`)}

Review the current settings before retrying a failed agent. Changes that succeeded are already saved.

`; const title = () => mode === 'share' ? view.replace ? 'Use ' + S.nameOf(view.source) + '’s version of ' + view.name : 'Share ' + view.name : mode === 'edit' ? 'Edit ' + view.name : 'Add MCP server'; view.layer = Kit.open({ kind: 'center', panel: { class: 'tc-wizard tc-mcp-editor', role: 'dialog', 'aria-modal': 'true', 'aria-labelledby': 'mcp-editor-title' }, backdrop: 'ignore', onEscape: () => { void closeDraft('escape'); }, @@ -310,6 +310,6 @@ const T3McpEditor = (() => { }; return { open, close, remove, repaint: () => active?.repaint() }; }; - const formatName = value => ({ 'claude-json': 'Claude Code or Cursor JSON', 'codex-toml': 'Codex TOML', 'opencode-json': 'OpenCode JSON', 'claude-mcp-add': 'claude mcp add', url: 'URL', command: 'command line', json: 'JSON' }[value] || value); + const formatName = value => ({ 'claude-json': 'MCP JSON', 'muse-json': 'Muse Code JSON', 'codex-toml': 'Codex or Grok TOML', 'opencode-json': 'OpenCode JSON', 'claude-mcp-add': 'claude mcp add', url: 'URL', command: 'command line', json: 'JSON' }[value] || value); return { create }; })(); diff --git a/docker/setup/client/mcp-inspector.js b/docker/setup/client/mcp-inspector.js index 393a7c6a..43bbeddb 100644 --- a/docker/setup/client/mcp-inspector.js +++ b/docker/setup/client/mcp-inspector.js @@ -51,12 +51,12 @@ const T3McpInspector = (() => { return html`
${ctx.mark(p.id)}
${p.name}${differs ? html` Differs` : ''}${state.reason || (differs ? 'Its copy doesn’t match the shared server' : added ? 'Matches the shared server' : 'Not added')}
${pending ? html`` : state.kind === 'error' ? '' : differs ? html`${Kit.isPhone() ? html`` : ''}` : added && p.canDisable ? html`` : added ? html`` : html`${ctx.undos.get(p.id)?.name === name ? html`` : ''}`} ${differs && expanded ? diffBox(p) : ''}
`; - })}${current().installed.includes('grok') && native('grok')?.imports?.length ? html`
${ctx.mark('grok')}
Grok Build${'May import ' + S.nameOf(S.state(current().data, sharedRow(), 'grok').source || 'claude') + '’s copy. Not managed here.'}
${S.state(current().data, sharedRow(), 'grok').label}
` : ''}

Each agent reads its own copy when a session starts.

`; + })}

Each agent reads its own copy when a session starts.

`; const connection = () => { const server = view.server, remote = server.type === 'remote', pairs = Object.entries(remote ? server.headers || {} : server.env || {}); return html`

Connection

Transport
${remote ? 'Streamable HTTP' : 'Command'}
${remote ? 'URL' : 'Command'}
${remote ? html`${server.url}` : html`${server.command.map((part, index) => html`${part || '""'}`)}`}
${pairs.map(([key, value], index) => html`
${remote ? 'Header' : 'Variable'}
${key}${view.revealed.has(key) ? value : '••••••••••••'}
`)}
`; }; - const signIn = () => view.server.type !== 'remote' ? '' : html`
Sign-inNot shared between agents

Only needed when ${name} asks to sign in. Run it in a T3 Code terminal.

`; + const signIn = () => view.server.type !== 'remote' ? '' : html`
Sign-inNot shared between agents

Only needed when ${name} asks to sign in. Run it in a T3 Code terminal. Grok, Pi and Antigravity also offer sign-in in their native MCP menu.

`; view.layer = Kit.open({ kind: 'sheet', returnTo: document.activeElement, panel: { class: 'tc-sheet tc-skill-inspector tc-mcp-inspector' + (Kit.isPhone() ? ' tc-sheet--bottom' : ''), role: 'dialog', 'aria-modal': 'true', 'aria-labelledby': 'mcp-inspect-title' }, focus: '[data-mcp-inspect="close"]', backdrop: 'ignore', onEscape: () => close('escape'), onClose: () => { view.revealed.clear(); view.differences.clear(); view.server = null; if (active === view) active = null; }, render: () => html`

${name}

${view.refreshing ? html`` : ''}Shared server${view.server ? ' · ' + (view.server.type === 'remote' ? 'Remote' : 'Local') + ' · ' + M.plural(Object.keys(view.server.headers || view.server.env || {}).length, view.server.type === 'remote' ? 'header' : 'variable') : ''}
${view.error ? html`` : view.loading ? html`

Reading server…

` : view.server ? html`${[...ctx.mutationErrors.values()].filter(e => e.name === name).map(error => html``)}${agentRows()}${connection()}${signIn()}

Where it’s saved

${ctx.pathLabel(current().data?.shared?.path || '~/.t3/setup/mcp/servers.json')}

Backups: Beside each agent’s settings file when it changes. They hold secret values.

` : ''}
` }); @@ -95,6 +95,6 @@ const T3McpInspector = (() => { (panel.querySelector('[data-mcp-policy="' + CSS.escape(provider) + '"]') || panel.querySelector('[data-mcp-inspect="add"][data-provider="' + CSS.escape(provider) + '"]') || panel.querySelector('[data-mcp-inspect="close"]'))?.focus({ preventScroll: true }); } }; }; - const signInCommand = (id, name) => ({ claude: 'claude mcp login ', codex: 'codex mcp login ', opencode: 'opencode mcp auth ', cursor: 'cursor-agent mcp login ' }[id] || '') + name; + const signInCommand = (id, name) => ({ claude: 'claude mcp login ', codex: 'codex mcp login ', opencode: 'opencode mcp auth ', cursor: 'cursor-agent mcp login ', muse: 'muse mcp login ', pi: 'pi mcp login ' }[id] || '') + (['claude', 'codex', 'opencode', 'cursor', 'muse', 'pi'].includes(id) ? name : ''); return { create, changed, signInCommand }; })(); diff --git a/docker/setup/client/mcp-model.js b/docker/setup/client/mcp-model.js index 0118239c..f2555de4 100644 --- a/docker/setup/client/mcp-model.js +++ b/docker/setup/client/mcp-model.js @@ -7,9 +7,10 @@ const T3McpModel = (() => { { id: 'codex', name: 'Codex', control: 'On or off', managed: true, canDisable: true }, { id: 'opencode', name: 'OpenCode', control: 'On or off', managed: true, canDisable: true }, { id: 'cursor', name: 'Cursor', control: 'Add or remove', managed: true }, - { id: 'grok', name: 'Grok Build', control: 'Imports only' }, - { id: 'muse', name: 'Muse Code', control: 'Not managed here' }, - { id: 'antigravity', name: 'Antigravity', control: 'Not managed here' }, + { id: 'grok', name: 'Grok Build', control: 'On or off', managed: true, canDisable: true }, + { id: 'muse', name: 'Muse Code', control: 'On or off', managed: true, canDisable: true }, + { id: 'antigravity', name: 'Antigravity', control: 'On or off', managed: true, canDisable: true }, + { id: 'pi', name: 'Pi', control: 'On or off', managed: true, canDisable: true }, ]; const columns = (installed, all = false) => providers.filter(provider => all || installed.includes(provider.id)); const normalize = value => Array.isArray(value) ? value.map(normalize) : value && typeof value === 'object' @@ -25,10 +26,6 @@ const T3McpModel = (() => { const has = (provider, name) => ['linked', 'diverged'].some(key => provider?.[key]?.includes(name)) || provider?.own?.some(row => row.name === name); const state = (data, row, id) => { const provider = data?.providers?.find(p => p.provider === id); - if (id === 'grok') { - const source = (provider?.imports || []).find(id => has(data?.providers?.find(p => p.provider === id), row.name)); - return { kind: 'via', label: source ? 'Via ' + nameOf(source) : '—', source }; - } if (!provider?.supported) return { kind: 'unsupported', label: '—', reason: provider?.error || 'Not managed here.' }; if (provider.error) return { kind: 'error', label: '—', reason: provider.error }; if (!row.shared && provider.own?.some(entry => entry.name === row.name)) return { kind: 'own', label: 'Own entry' }; diff --git a/docker/setup/client/model.js b/docker/setup/client/model.js index 65b6f15b..5a7763d3 100644 --- a/docker/setup/client/model.js +++ b/docker/setup/client/model.js @@ -2033,9 +2033,9 @@ const T3Model = (() => { return { present: Boolean(file.present), bytes: file.bytes || 0, error: file.error || null }; }; - const PROVIDER_NAMES = { claude: 'Claude Code', codex: 'Codex', opencode: 'OpenCode' }; + const PROVIDER_NAMES = { claude: 'Claude Code', codex: 'Codex', opencode: 'OpenCode', cursor: 'Cursor', grok: 'Grok Build', muse: 'Muse Code', antigravity: 'Antigravity', pi: 'Pi' }; // The file each agent reads on its own when it is not using the shared one. - const OWN_FILE = { claude: 'CLAUDE.md', codex: 'AGENTS.md', opencode: 'AGENTS.md' }; + const OWN_FILE = { claude: 'CLAUDE.md', grok: 't3-instructions.md' }; /** The shared AGENTS.md: how big it is, and which agents read it. */ const contextAgentsMdRow = (status) => { @@ -2078,7 +2078,7 @@ const T3Model = (() => { : providers.filter((file) => installed.has(file.provider) || file.mode || file.error); return list.map((file) => { const name = PROVIDER_NAMES[file.provider] || file.provider; - const own = OWN_FILE[file.provider] || 'AGENTS.md'; + const own = file.path?.split('/').at(-1) || OWN_FILE[file.provider] || 'AGENTS.md'; return { id: file.provider, key: 'agentsmd:' + file.provider, @@ -2088,7 +2088,7 @@ const T3Model = (() => { status: file.error ? { dot: 'danger', text: file.error } : file.mode === 'shared' ? { text: 'Reads the shared AGENTS.md' } : file.mode === 'separate' ? { text: 'Reads its own ' + own } - : { text: 'Reads no global instructions' }, + : { text: 'No managed global instructions' }, // What the row says under its name, beside the choice that already // says what it reads: where its own file is, or why it reads nothing. note: file.error ? null : file.mode === 'separate' ? file.path || null diff --git a/docker/setup/client/skill-import.js b/docker/setup/client/skill-import.js index e61585e8..82f5ed03 100644 --- a/docker/setup/client/skill-import.js +++ b/docker/setup/client/skill-import.js @@ -133,7 +133,7 @@ const T3SkillImport = (() => { const agentRow = (provider, destination) => { const id = provider.id, selected = view.agents.has(id), duplicate = planner().duplicates.includes(id); const own = id === destination || planner().destinations.find(folder => folder.provider === destination)?.also.includes(id); - return html``; + return html``; }; const agentPicker = () => { const placement = planner(), groups = view.plan ? destinationGroups(placements()) : placement.destinations.map(destination => ({ key: destination.root?.path || destination.provider, provider: destination.provider, entries: [] })); diff --git a/docker/setup/client/skill-inspector.js b/docker/setup/client/skill-inspector.js index 393d04b5..39f220bd 100644 --- a/docker/setup/client/skill-inspector.js +++ b/docker/setup/client/skill-inspector.js @@ -11,7 +11,7 @@ const T3SkillInspector = (() => { }; const create = ({ api, getLibrary, getStatus, availability, refresh, readMarkdown, downloadMarkdown }) => { let active = null; - const repaint = () => { if (active && !active.layer.closed) active.layer.render(); }; + const repaint = () => { if (active && !active.layer.closed) { active.reconcile?.(); active.layer.render(); } }; const open = async (key, editing = false, creating = false) => { if (active && !await active.close('replaced')) return; const library = getLibrary(); @@ -28,12 +28,54 @@ const T3SkillInspector = (() => { active = view; const editor = T3CodeEditor.create({ id: 'skill-content', label: 'SKILL.md contents', describedBy: 'skill-editor-meta' }); const findCurrent = lookup => getLibrary().rows.find(row => row.key === lookup) || getLibrary().physicalRows?.find(row => row.key === lookup); + let selected = skill?.unified ? skill.installations.find(row => row.key === view.selectedKey) : skill; const row = () => { - if (!skill?.unified) return findCurrent(key) || skill; - return findCurrent(view.selectedKey) || skill.installations.find(row => row.key === view.selectedKey) || skill; + let current = findCurrent(view.selectedKey); + const references = Object.entries(selected?.byProvider || {}).filter(([, entry]) => entry.placementKey); + const matches = copy => references.some(([provider, entry]) => copy?.byProvider[provider]?.placementKey === entry.placementKey); + // A cached compatibility reader can still occupy the old physical + // key. Keep following the provider-local installation that moved. + if (references.length && !matches(current)) current = null; + if (!current && references.length) { + const moved = (getLibrary().physicalRows || []).filter(matches); + if (moved.length === 1) current = moved[0]; + } + if (current && references.length) { + const [provider] = references.find(([provider, entry]) => current.byProvider[provider]?.placementKey === entry.placementKey); + current = { ...current, ...current.byProvider[provider], provider }; + } + if (current) { selected = current; view.selectedKey = current.key; } + return current || selected || skill; + }; + const identity = current => current && current.provider + ':' + current.byProvider[current.provider].id + ':' + current.path; + let readIdentity = null, loadedIdentity = null, readSequence = 0; + const detail = () => { + const current = row(); + if (!view.data) return current; + const fresh = loadedIdentity === identity(current); + return { ...view.data, path: current.path, resolvedPath: current.resolvedPath, + canEdit: fresh && current.canEdit, canRemove: fresh && current.canRemove, + readOnlyReason: current.readOnlyReason || (!fresh ? 'Refresh skill details before editing or removing this installation.' : null) }; }; - const detail = () => view.data || row(); const dirty = () => view.draft !== view.original || (creating && Boolean(view.slug)); + const readDetails = async (current, preserveDraft = false) => { + const request = ++readSequence, requestedIdentity = identity(current); + readIdentity = requestedIdentity; view.loading = true; view.error = null; + const response = await api('/context/skills/read?provider=' + encodeURIComponent(current.provider) + '&id=' + encodeURIComponent(current.byProvider[current.provider].id)); + if (active !== view || view.layer.closed || request !== readSequence) return; + view.loading = false; + if (response.ok) { + view.data = response.data; loadedIdentity = requestedIdentity; + if (!preserveDraft || !view.editing || !dirty()) view.draft = view.original = response.data.content; + } else view.error = response.error; + repaint(); + }; + // Placement changes the native path and id. Follow that installation, + // refresh its file revisions and keep any unsaved draft in this layer. + view.reconcile = () => { + const current = row(); + if (readIdentity && current && identity(current) !== readIdentity) void readDetails(current, true); + }; const beforeUnload = event => { if (view.saving || view.editing && dirty()) { event.preventDefault(); event.returnValue = ''; } }; const filename = () => ((creating ? view.slug : skill.name) || 'skill').replace(/[^a-z0-9-]/gi, '-').slice(0, 64) + '.md'; const download = () => downloadMarkdown(filename(), view.draft); @@ -47,7 +89,7 @@ const T3SkillInspector = (() => { }; const renderLocations = () => { if (skill?.unified) { - const installations = skill.installations || []; + const installations = (getLibrary().physicalRows || []).filter(copy => copy.name === skill.name); const file = detail(), status = getStatus(), target = file.resolvedPath || file.path; const stateVolume = status?.paths?.stateKind === 'durable' && S.contains(status.paths.state, target); const homeVolume = status?.paths?.volumeKind === 'durable' && S.contains(status.paths.volume, target); @@ -102,7 +144,7 @@ const T3SkillInspector = (() => { const found = current.byProvider[provider.id], unavailable = getLibrary().errors[provider.id]; return html`
${provider.name}${unavailable ? html`Couldn’t check this agent’s skills` : !found ? html`Not in a folder ${provider.name} reads` : found.effectiveStateText ? html`${found.effectiveStateText}` : ''}
${unavailable ? html`` : found ? availability(current, provider.id, true) : html``}
`; - })}

Each setting is the agent’s own and applies to new sessions.

`; + })}

Settings apply to new sessions.${Object.values(current.byProvider).some(entry => entry.stateAuthority === 'placement') ? ' Moving a skill also affects other agents reading that folder.' : ' Each agent keeps its own setting.'}

`; }; const renderEditor = () => html`
${view.editing ? html`` : ''} @@ -121,7 +163,7 @@ const T3SkillInspector = (() => { view.layer = Kit.open({ kind: 'sheet', returnTo: document.activeElement, panel: { class: 'tc-sheet tc-skill-inspector' + (Kit.isPhone() ? ' tc-sheet--bottom' : ''), role: 'dialog', 'aria-modal': 'true', 'aria-labelledby': 'skill-view-title' }, focus: '[data-inspect-close]', backdrop: 'ignore', onEscape: () => { if (!editor.escape()) void view.close('escape'); }, - onRender: layer => editor.sync(layer.panel, { value: view.draft, disabled: view.loading || view.saving, readOnly: !view.editing }), + onRender: layer => editor.sync(layer.panel, { value: view.draft, disabled: view.loading || view.saving, readOnly: !view.editing || !creating && !detail()?.canEdit }), onClose: () => { editor.destroy(); window.removeEventListener('beforeunload', beforeUnload); if (active === view) active = null; }, render: () => html`

${creating ? 'New skill' : skill.name}

${creating ? 'Write instructions for your agents' : html`${skill.unified ? 'Unified · ' + M.plural(skill.installations.length, 'location') : skill.groupLabel}·${detail()?.canEdit ? 'Editable' : 'Read-only'}${view.data?.files ? html`·${T3Model.plural(view.data.files.length, 'file')}` : ''}${view.data ? html`·${bytes(view.data.bytes)}` : ''}`}
@@ -131,7 +173,7 @@ const T3SkillInspector = (() => {
${footer()}
` }); window.addEventListener('beforeunload', beforeUnload); const syncEditor = () => { - editor.sync(view.layer.panel, { value: view.draft, disabled: view.loading || view.saving, readOnly: !view.editing }); + editor.sync(view.layer.panel, { value: view.draft, disabled: view.loading || view.saving, readOnly: !view.editing || !creating && !detail()?.canEdit }); const save = view.layer.panel.querySelector('[data-inspect-save]'); if (save) save.disabled = view.loading || view.saving || (creating && !view.slug.trim()); const size = view.layer.panel.querySelector('[data-editor-bytes]'); if (size) size.textContent = bytes(new TextEncoder().encode(view.draft).length); @@ -154,13 +196,8 @@ const T3SkillInspector = (() => { } view.selectedKey = event.target.value; view.data = null; view.draft = view.original = ''; view.error = null; view.linkError = null; view.editing = false; view.loading = true; repaint(); - const current = row(); - const response = await api('/context/skills/read?provider=' + encodeURIComponent(current.provider) + '&id=' + encodeURIComponent(current.byProvider[current.provider].id)); - if (active !== view) return; - view.loading = false; - if (response.ok) { view.data = response.data; view.draft = view.original = response.data.content; } - else view.error = response.error; - repaint(); + selected = skill.installations.find(copy => copy.key === view.selectedKey) || selected; + await readDetails(row()); return; } if (event.target.id !== 'skill-file' || !event.target.files?.[0]) return; @@ -237,7 +274,7 @@ const T3SkillInspector = (() => { if ((action === 'data-inspect-download')) { download(); return; } if ((action === 'data-inspect-copy-path')) { Kit.copy(S.directory(detail().path), button, 'Skill folder path copied'); return; } if ((action === 'data-inspect-upload')) { view.layer.panel.querySelector('#skill-file').click(); return; } - if ((action === 'data-inspect-edit')) { view.editing = true; repaint(); view.layer.panel.querySelector('#skill-content')?.focus(); return; } + if ((action === 'data-inspect-edit')) { if (!detail()?.canEdit || view.loading) return; view.editing = true; repaint(); view.layer.panel.querySelector('#skill-content')?.focus(); return; } if (action === 'data-inspect-review-source') { const current = row(); if (await view.close('review-source')) getLibrary().reviewUpdate(current); @@ -250,6 +287,7 @@ const T3SkillInspector = (() => { } const remove = (action === 'data-inspect-remove'); if (!remove && !(action === 'data-inspect-save') || view.saving || view.loading) return; + if (!creating && !(remove ? detail()?.canRemove : detail()?.canEdit)) return; if (remove) { const yes = await Kit.confirm({ title: 'Remove ' + skill.name + '?', body: view.data.removalKind === 'directory' ? 'Removes this local skill folder and its supporting files. Other agents using this folder lose it too.' : view.data.removalLinkedTarget ? 'Removes this installation entry. Linked target files are kept.' : 'Removes this Markdown file. Other files stay in place.', consequences: [{ icon: 'trash-2', text: view.data.removalPath }], confirm: 'Remove skill', tone: 'danger' }); if (!yes || active !== view) return; @@ -271,12 +309,9 @@ const T3SkillInspector = (() => { if (action) void act(action, button); }); if (creating) return; - const initialRow = row(); - const response = await api('/context/skills/read?provider=' + encodeURIComponent(initialRow.provider) + '&id=' + encodeURIComponent(initialRow.byProvider[initialRow.provider].id)); + await readDetails(row()); if (active !== view || view.layer.closed) return; - view.loading = false; - if (response.ok) { view.data = response.data; view.draft = view.original = response.data.content; view.editing = editing && Boolean(response.data.canEdit); } - else { view.error = response.error; view.editing = false; } + view.editing = editing && Boolean(view.data && detail()?.canEdit); repaint(); if (view.editing) view.layer.panel.querySelector('#skill-content')?.focus(); }; diff --git a/docker/setup/client/skill-model.js b/docker/setup/client/skill-model.js index 4f3c1c57..e30c991a 100644 --- a/docker/setup/client/skill-model.js +++ b/docker/setup/client/skill-model.js @@ -5,10 +5,11 @@ const T3SkillModel = (() => { { id: 'claude', name: 'Claude Code', control: 'Visibility' }, { id: 'codex', name: 'Codex', control: 'On or off' }, { id: 'opencode', name: 'OpenCode', control: 'Permission' }, - { id: 'cursor', name: 'Cursor', control: 'Not reported' }, - { id: 'grok', name: 'Grok Build', control: 'Not reported' }, - { id: 'muse', name: 'Muse Code', control: 'Not reported' }, - { id: 'antigravity', name: 'Antigravity', control: 'Not reported' }, + { id: 'cursor', name: 'Cursor', control: 'Placement' }, + { id: 'grok', name: 'Grok Build', control: 'On or off' }, + { id: 'muse', name: 'Muse Code', control: 'Visibility' }, + { id: 'antigravity', name: 'Antigravity', control: 'Placement' }, + { id: 'pi', name: 'Pi', control: 'On or off' }, ]); const nameOf = id => providers.find(p => p.id === id)?.name || id; const pluginName = id => { diff --git a/docker/setup/client/skills.js b/docker/setup/client/skills.js index d1376f4f..f68e0d5b 100644 --- a/docker/setup/client/skills.js +++ b/docker/setup/client/skills.js @@ -110,17 +110,24 @@ const T3SkillsUI = (() => { const enabled = row.canToggle ? row.state !== 'enabled' : undefined; const response = await api('/context/skills/state', { body: { provider, id, stateRevision: row.stateRevision, enabled, state: value } }); changing.delete(provider); - if (response.ok) { inventories[provider] = response.data.inventory; delete errors[provider]; rebuild(); paint(); Kit.toast('Saved ' + S.nameOf(provider) + ' setting'); Kit.announce('Saved ' + S.nameOf(provider) + '’s skill setting'); } + if (response.ok) { + inventories[provider] = response.data.inventory; delete errors[provider]; rebuild(); paint(); + // A placement move also changes discovery for compatibility readers. + if (row.stateAuthority === 'placement') await loadAll(true); + Kit.toast('Saved ' + S.nameOf(provider) + ' setting'); Kit.announce('Saved ' + S.nameOf(provider) + '’s skill setting'); + } else { Kit.toast('Couldn’t change ' + S.nameOf(provider) + '’s setting for ' + row.name, { tone: 'danger', detail: response.error }); await loadProvider(provider, true); } if (focusedKey && (document.activeElement === document.body || document.activeElement?.classList.contains('tc-skill-inspector'))) document.querySelector('[data-key="' + CSS.escape(focusedKey) + '"]')?.focus({ preventScroll: true }); }; const policyMenu = (id, provider, trigger) => { const row = inventories[provider]?.skills.find(row => row.id === id); if (!row?.canSetState || changing.has(provider)) return; - const explanations = { on: 'Claude sees its name and description. You can run /' + row.name + '.', 'name-only': 'Claude sees its name without the description, which saves context.', 'user-invocable-only': 'Hidden from Claude. You can still run /' + row.name + '.', off: 'Hidden from Claude and from the / menu.', allow: 'The agent can use this skill.', ask: 'The agent asks before using this skill.', deny: 'The agent cannot use this skill.' }; - Kit.menu(trigger, { label: S.nameOf(provider) + ' skill setting', head: { title: S.nameOf(provider) + ' · ' + (provider === 'claude' ? 'visibility' : 'permission') }, + const explanations = provider === 'claude' + ? { on: 'Claude sees its name and description. You can run /' + row.name + '.', 'name-only': 'Claude sees its name without the description, which saves context.', 'user-invocable-only': 'Hidden from Claude. You can still run /' + row.name + '.', off: 'Hidden from Claude and from the / menu.' } + : { on: 'Available for the agent to discover and use.', enabled: 'Available for the agent to discover and use.', 'user-invocable-only': 'Available when you invoke it explicitly.', off: 'Excluded by your saved skill settings.', disabled: 'Excluded by your saved skill settings.', allow: 'The agent can use this skill.', ask: 'The agent asks before using this skill.', deny: 'The agent cannot use this skill.' }; + Kit.menu(trigger, { label: S.nameOf(provider) + ' skill setting', head: { title: S.nameOf(provider) + ' · ' + (provider === 'claude' ? 'visibility' : provider === 'opencode' ? 'permission' : 'availability') }, items: row.stateOptions.map(option => ({ label: option.label, description: explanations[option.value], radio: true, checked: option.value === row.state, run: () => setState(id, provider, option.value) })), - footer: html`${row.statePath ? html`Saved in ${S.displayPath(row.statePath, library().home)}. ` : ''}Applies to new sessions.${provider === 'opencode' && row.effectiveStateText ? html`
${row.effectiveStateText}` : ''}` }); + footer: html`${row.statePath ? html`Saved in ${S.displayPath(row.statePath, library().home)}. ` : ''}Applies to new sessions.${row.effectiveStateText ? html`
${row.effectiveStateText}` : ''}` }); }; const findRow = key => displayRows().find(row => row.key === key) || rows.find(row => row.key === key); const expandKeys = keys => { diff --git a/docker/setup/console.css b/docker/setup/console.css index e159ec18..2f67cbe8 100644 --- a/docker/setup/console.css +++ b/docker/setup/console.css @@ -1332,7 +1332,7 @@ html.tc-locked { overflow: hidden; } .tc-wizard-rail-wrap { display: flex; flex-direction: column; } -.tc-wizard-rail { flex: 1; min-height: 0; } +.tc-wizard-rail { flex: 1 0 auto; min-height: 0; } .tc-wizard-rail > * { flex-shrink: 0; } .tc-dest-name { font-weight: 500; } .tc-wizard-rail-toggle { border: 0; background: transparent; color: var(--fg); font: inherit; text-align: left; } diff --git a/docker/setup/context.mjs b/docker/setup/context.mjs index 33b00e22..eba93322 100644 --- a/docker/setup/context.mjs +++ b/docker/setup/context.mjs @@ -3,7 +3,7 @@ // and links are preserved; existing files change only on an explicit save. import path from "node:path"; import { createHash, randomBytes } from "node:crypto"; -import { opencodeConfigRoot } from "./opencode-config.mjs"; +import { agentResourcePaths } from "./agent-resources.mjs"; export function contextDirFor(baseDir) { return path.join(baseDir, "setup", "context"); @@ -13,19 +13,29 @@ export function agentsMdPathFor(baseDir) { return path.join(contextDirFor(baseDir), "AGENTS.md"); } -/** Native global instruction paths; other providers are deferred. */ +/** Cursor's dedicated rule workspace is loaded by the T3 SDK bridge. */ export function agentsMdTargets(env = {}, home = "/home/t3") { - const claudeDir = String(env.CLAUDE_CONFIG_DIR ?? "").trim() || path.join(home, ".claude"); - const codexHome = String(env.CODEX_HOME ?? "").trim() || path.join(home, ".codex"); - // OpenCode reads its config dir through OPENCODE_CONFIG_DIR; the entrypoint - // points it at a real folder on the state volume, so instructions belong - // there rather than under a symlinked ~/.config/opencode. - const openCodeHome = opencodeConfigRoot(home, env); - return [ - { provider: "claude", path: path.join(claudeDir, "CLAUDE.md") }, - { provider: "codex", path: path.join(codexHome, "AGENTS.md") }, - { provider: "opencode", path: path.join(openCodeHome, "AGENTS.md") }, - ]; + const roots = agentResourcePaths(home, env); + return Object.entries({ + claude: "CLAUDE.md", codex: "AGENTS.md", opencode: "AGENTS.md", + cursor: "t3-instructions/AGENTS.md", grok: "rules/t3-instructions.md", + muse: "AGENTS.md", antigravity: "AGENTS.md", pi: "AGENTS.md", + }).map(([provider, name]) => ({ provider, path: path.join(roots[provider], name) })); +} + +// Pi and Codex choose one context file by precedence. Edit the file they +// actually load, preserving an existing override instead of shadowing it. +async function instructionTargets(fs, env = {}, home = "/home/t3") { + return Promise.all(agentsMdTargets(env, home).map(async target => { + const names = target.provider === "pi" ? ["AGENTS.override.md", "AGENTS.md", "AGENTS.MD", "CLAUDE.md", "CLAUDE.MD"] + : target.provider === "codex" ? ["AGENTS.override.md", "AGENTS.md"] : null; + if (names) for (const name of names) { + const file = path.join(path.dirname(target.path), name); + try { await fs.lstat(file); return { ...target, path: file }; } + catch (error) { if (error.code !== "ENOENT") throw error; } + } + return target; + })); } const MAX_INSTRUCTION_BYTES = 64 * 1024; @@ -154,7 +164,7 @@ export async function deleteAgentsMd(fs, baseDir, revision = null, { home = "/ho const canonical = path.resolve(file); await fs.rm(file, { force: true }); let unlinked = 0; - for (const target of agentsMdTargets(env, home)) { + for (const target of await instructionTargets(fs, env, home)) { try { const stat = await fs.lstat(target.path); if (!stat.isSymbolicLink()) continue; @@ -179,7 +189,7 @@ export async function reconcileAgentsMd(fs, baseDir, { home = "/home/t3", env = const allowed = only ? new Set(only) : null; const linked = []; const skipped = []; - for (const target of agentsMdTargets(env, home)) { + for (const target of await instructionTargets(fs, env, home)) { if (allowed && !allowed.has(target.provider)) continue; try { try { @@ -205,7 +215,7 @@ export async function reconcileAgentsMd(fs, baseDir, { home = "/home/t3", env = /** Read a known provider's existing file, following its symlinks. */ export async function readProviderAgentsMd(fs, provider, { home = "/home/t3", env = {} } = {}) { - const target = agentsMdTargets(env, home).find((entry) => entry.provider === provider); + const target = (await instructionTargets(fs, env, home)).find((entry) => entry.provider === provider); if (!target) return { ok: false, code: "invalid", error: "Unknown instruction provider" }; let resolvedPath; try { @@ -238,7 +248,7 @@ export async function writeProviderAgentsMd(fs, provider, content, revision, opt /** The native file itself records whether a provider uses shared or separate instructions. */ export async function providerInstructionState(fs, baseDir, provider, options = {}) { - const target = agentsMdTargets(options.env, options.home).find((entry) => entry.provider === provider); + const target = (await instructionTargets(fs, options.env, options.home)).find((entry) => entry.provider === provider); if (!target) return { ok: false, code: "invalid", error: "Unknown instruction provider" }; let entry; try { entry = await fs.lstat(target.path); } @@ -333,7 +343,7 @@ export async function contextStatus(fs, { baseDir, home = "/home/t3", env = {} } const snapshotFs = { ...fs, [instructionReads]: new Map() }; const read = await readAgentsMd(snapshotFs, baseDir); const providers = []; - for (const target of agentsMdTargets(env, home)) { + for (const target of await instructionTargets(fs, env, home)) { const state = await providerInstructionState(snapshotFs, baseDir, target.provider, { home, env }); providers.push({ ...target, mode: state.mode, sourceRevision: state.sourceRevision, bytes: state.bytes, modifiedAt: state.modifiedAt, error: state.error ?? null }); } diff --git a/docker/setup/gateways.mjs b/docker/setup/gateways.mjs index f74a25ad..ce7af481 100644 --- a/docker/setup/gateways.mjs +++ b/docker/setup/gateways.mjs @@ -629,8 +629,30 @@ export function createGatewayService({ baseDir, home, env = process.env, io, t3, return { ok: true, ...await status() }; }); + // Native user MCP/skill saves must also reach gateway-owned Grok homes. + // Refresh behavior sections without touching keys or interrupting a turn. + const syncNativeResources = provider => serial(async () => { + if (provider !== 'grok') return; + const { gateways } = await store.read(); + const connected = gateways.filter(gateway => gateway.agents.grok?.on); + if (!connected.length) return; + const state = await requireT3(), agent = AGENTS.find(agent => agent.id === 'grok'); + const results = []; + for (const gateway of connected) { + try { + await writeAgentFiles(agent, gateway, undefined, { quiet: true, instances: state.instances }); + results.push({ id: gateway.id, ok: true }); + } catch { + results.push({ id: gateway.id, ok: false }); + } + } + invalidate(); + if (results.some(result => !result.ok)) throw new GatewayError('Native settings were saved, but a Grok gateway profile could not be updated. Use Apply on that gateway before starting a new session.', 409, 'partial'); + return { updated: results.length }; + }); + const actions = { '/gateways/test': test, '/gateways/create': create, '/gateways/save': save, '/gateways/agent': setAgent, '/gateways/limits': setLimits, '/gateways/limits/refresh': refreshLimits, '/gateways/apply': reapply, '/gateways/models': refreshModels, '/gateways/remove': remove, '/gateways/conflict': clearConflict }; - return { status, summary, test, create, save, setAgent, setLimits, refreshLimits, reapply, refreshModels, remove, clearConflict, + return { status, summary, test, create, save, setAgent, setLimits, refreshLimits, reapply, refreshModels, remove, clearConflict, syncNativeResources, /** One entry point for the server. `route` is one of GATEWAY_ROUTES. */ handle: (route, input) => route === '/gateways' ? status() : actions[route]?.(input) ?? Promise.reject(new GatewayError('Gateway action not found.', 404, 'not-found')) }; } diff --git a/docker/setup/mcp.mjs b/docker/setup/mcp.mjs index f38b3eed..80931d0a 100644 --- a/docker/setup/mcp.mjs +++ b/docker/setup/mcp.mjs @@ -3,7 +3,7 @@ import path from 'node:path'; import { mcpStorePathFor, readMcpStore, createMcpServer, saveMcpServer, removeMcpServer, adoptMcpServer, redactMcpServer, validateMcpServer, MCP_PROVIDERS, UNSUPPORTED_MCP_PROVIDERS, withStore } from './mcp/store.mjs'; -import { readProviderMcp, writeProviderMcpEntry, toClaudeEntry, toCodexEntry, toOpenCodeEntry, toCursorEntry, isSupportedMcpProvider } from './mcp/adapters.mjs'; +import { readProviderMcp, writeProviderMcpEntry, toClaudeEntry, toCodexEntry, toOpenCodeEntry, toCursorEntry, toAdditionalEntry, isSupportedMcpProvider } from './mcp/adapters.mjs'; import { parseMcpSnippet } from './mcp/parse.mjs'; const problem = (code, message) => Object.assign(new Error(message), { code }); @@ -40,13 +40,21 @@ export function mcpDifferences(shared, row) { return { fields, extraOptions: row.extraOptions || [], adoptable: true, dropped: row.extraOptions || [] }; } -export function createMcpService({ home, env = process.env, io, baseDir, ttlMs = 15000 } = {}) { +export function createMcpService({ home, env = process.env, io, baseDir, getHarness = async () => null, onNativeChange = async () => {}, ttlMs = 15000 } = {}) { if (!io) throw new Error('mcp service needs an fs implementation'); home = path.resolve(home || env.HOME || '/home/t3'); baseDir = baseDir || env.T3CODE_HOME || path.join(home, '.t3'); let cached = null, at = 0; const invalidate = () => { cached = null; at = 0; }; const native = provider => readProviderMcp(io, provider, { home, env }); + const capabilityError = async provider => { + if (provider !== 'pi') return null; + const harness = await getHarness(provider), version = harness?.installedVersion || harness?.version; + if (!harness?.installed && !harness?.runnable) return null; + const match = /^(\d+)\.(\d+)\.(\d+)(?:$|[-+])/.exec(version || ''); + if (match && (Number(match[1]) > 0 || Number(match[2]) >= 99)) return null; + return 'Update Pi to 0.99.0 or later to use native MCP servers. This installed version does not have verified mcp.json support.'; + }; const storeRead = async () => { const store = await readMcpStore(io, baseDir); if (store.error) throw problem(store.code || 'unavailable', store.error); @@ -74,9 +82,13 @@ export function createMcpService({ home, env = process.env, io, baseDir, ttlMs = if (!refresh && cached && Date.now() - at < ttlMs) return cached; const promise = (async () => { const store = await readMcpStore(io, baseDir), reads = new Map(); - const providers = await Promise.all(['claude', 'codex', 'opencode', 'cursor', 'grok', 'muse', 'antigravity'].map(async provider => { + const providers = await Promise.all(Object.keys(MCP_PROVIDERS).map(async provider => { if (UNSUPPORTED_MCP_PROVIDERS[provider]) return { provider, supported: false, error: UNSUPPORTED_MCP_PROVIDERS[provider], ...(provider === 'grok' ? { imports: ['claude', 'cursor'] } : {}), linked: [], diverged: [], unlinked: [], disabled: [], own: [], ownCount: 0 }; - try { const read = await native(provider); reads.set(provider, read); return stateFor(provider, read, store.servers); } + try { + const unavailable = await capabilityError(provider); + if (unavailable) throw problem('upgrade-required', unavailable); + const read = await native(provider); reads.set(provider, read); return stateFor(provider, read, store.servers); + } catch (error) { return { provider, supported: true, revision: null, linked: [], diverged: [], unlinked: [], disabled: [], own: [], ownCount: 0, error: String(error?.message || error).slice(0, 240), code: error?.code }; } })); @@ -114,16 +126,20 @@ export function createMcpService({ home, env = process.env, io, baseDir, ttlMs = const setLink = async (provider, name, linked, sharedRevision, providerRevision, enabled) => withStore(mcpStorePathFor(baseDir), async () => { assertProvider(provider); if (typeof linked !== 'boolean' || (enabled !== undefined && typeof enabled !== 'boolean')) throw problem('invalid', 'Expected boolean linked and enabled values.'); - if (enabled === false && !['codex', 'opencode'].includes(provider)) throw problem('unsupported', `${MCP_PROVIDERS[provider]} has no global off switch. Remove it instead.`); + const unavailable = linked && await capabilityError(provider); + if (unavailable) throw problem('upgrade-required', unavailable); + if (enabled === false && !['codex', 'opencode', 'grok', 'muse', 'pi', 'antigravity'].includes(provider)) throw problem('unsupported', `${MCP_PROVIDERS[provider]} has no global off switch. Remove it instead.`); const store = await storeRead(); if (store.revision !== sharedRevision) throw problem('conflict', 'Shared MCP servers changed. Refresh the list before changing links.'); const server = getServer(store, name); const current = await native(provider), existing = current.servers.find(r => r.name === name); const on = enabled ?? existing?.canonical?.enabled ?? server.enabled !== false; const entry = !linked ? null : provider === 'codex' ? toCodexEntry(server, on) : provider === 'opencode' ? toOpenCodeEntry(server, false, on) - : provider === 'claude' ? toClaudeEntry(server) : toCursorEntry(server); + : provider === 'claude' ? toClaudeEntry(server) : provider === 'cursor' ? toCursorEntry(server) : toAdditionalEntry(provider, server, on); const result = await writeProviderMcpEntry(io, provider, { home, env }, name, entry, { revision: providerRevision }); - invalidate(); return { ...result, ok: true, provider, name, linked }; + invalidate(); + const propagation = await onNativeChange(provider); + return { ...result, ok: true, provider, name, linked, ...(propagation ? { propagation } : {}) }; }); const adopt = async (provider, name, providerRevision, sharedRevision, replace = false) => { assertProvider(provider); @@ -148,8 +164,9 @@ export function createMcpService({ home, env = process.env, io, baseDir, ttlMs = list: async provider => { assertProvider(provider); const store = await storeRead(), detail = await native(provider), state = stateFor(provider, detail, store.servers); + const unavailable = await capabilityError(provider); return { ok: true, ...state, sharedRevision: store.revision, shared: store.servers.map(s => ({ ...redactMcpServer(s), - linked: state.linked.includes(s.name), diverged: state.diverged.includes(s.name), linkable: true })), unsupported: detail.servers.filter(r => !r.canonical).map(r => r.name) }; + linked: state.linked.includes(s.name), diverged: state.diverged.includes(s.name), linkable: !unavailable })), ...(unavailable ? { error: unavailable, code: 'upgrade-required' } : {}), unsupported: detail.servers.filter(r => !r.canonical).map(r => r.name) }; }, create: mutation(createMcpServer), save: mutation(saveMcpServer), remove: async (name, revision) => { diff --git a/docker/setup/mcp/adapters.mjs b/docker/setup/mcp/adapters.mjs index ba31306d..1328c149 100644 --- a/docker/setup/mcp/adapters.mjs +++ b/docker/setup/mcp/adapters.mjs @@ -4,7 +4,9 @@ import path from "node:path"; import { parse, parseTree, modify, applyEdits } from "../vendor/jsonc.mjs"; import { parseMcpToml, applyMcpToml } from "./toml.mjs"; import { readConfig, changeConfig } from "./files.mjs"; -import { validateMcpServer } from "./store.mjs"; +import { validateMcpServer, SUPPORTED_MCP_PROVIDERS } from "./store.mjs"; +import { agentResourcePaths } from "../agent-resources.mjs"; +import { parse as parseToml, patch as patchToml } from "../vendor/toml-patch.mjs"; import { opencodeConfigRoot } from "../opencode-config.mjs"; const problem = (code, message) => Object.assign(new Error(message), { code }); @@ -13,7 +15,7 @@ const homeOf = (home, env) => path.resolve(home || env.HOME || "/home/t3"); export const claudeMcpPath = (home, env) => env.CLAUDE_CONFIG_DIR?.trim() ? path.join(path.resolve(homeOf(home, env), env.CLAUDE_CONFIG_DIR), ".claude.json") : path.join(homeOf(home, env), ".claude.json"); export const codexMcpPath = (home, env) => path.join(path.resolve(homeOf(home, env), env.CODEX_HOME || ".codex"), "config.toml"); -export const cursorMcpPath = (home, env) => path.join(homeOf(home, env), ".cursor", "mcp.json"); +export const cursorMcpPath = (home, env) => path.join(agentResourcePaths(homeOf(home, env), env).cursor, "mcp.json"); export const openCodeConfigPath = async (io, home, env) => { const root = opencodeConfigRoot(homeOf(home, env), env); const jsonc = path.join(root, "opencode.jsonc"); @@ -64,7 +66,28 @@ export function fromCursorEntry(name, entry) { if (!object(entry) || (entry.type !== undefined && !["http", "stdio"].includes(entry.type))) return null; return fromClaudeEntry(name, entry.type === undefined && typeof entry.url === "string" ? { ...entry, type: "http" } : entry); } -export const isSupportedMcpProvider = (provider) => ["claude", "codex", "opencode", "cursor"].includes(provider); +// These native formats infer transport from command/url and keep their own +// per-server enabled flags. Antigravity names the remote endpoint serverUrl. +export function toAdditionalEntry(provider, server, enabled = true) { + const entry = toClaudeEntry(server); + delete entry.type; + if (provider === "antigravity") { + if (entry.url) { entry.serverUrl = entry.url; delete entry.url; } + if (!enabled) entry.disabled = true; + } else if (!enabled) entry.enabled = false; + return entry; +} +export function fromAdditionalEntry(provider, name, entry) { + if (!object(entry)) return null; + if (entry.command !== undefined && (entry.url !== undefined || entry.serverUrl !== undefined)) return null; + if (entry.type !== undefined && !["stdio", "http", "streamable-http"].includes(entry.type)) return null; + const url = provider === "antigravity" ? entry.serverUrl ?? entry.url : entry.url; + const canonical = typeof url === "string" ? { name, type: "remote", url, headers: { ...(entry.headers ?? {}) }, enabled: true } + : typeof entry.command === "string" && entry.command ? { name, type: "local", command: [entry.command, ...(Array.isArray(entry.args) ? entry.args : [])], env: { ...(entry.env ?? {}) }, enabled: true } : null; + if (canonical) canonical.enabled = provider === "antigravity" ? entry.disabled !== true : entry.enabled !== false; + return canonical; +} +export const isSupportedMcpProvider = (provider) => SUPPORTED_MCP_PROVIDERS.includes(provider); export function jsonDocument(raw, strict) { const errors = []; @@ -82,7 +105,11 @@ export function jsonDocument(raw, strict) { } export function containerFor(provider, value) { - const key = provider === "opencode" ? "mcp" : "mcpServers"; + // The Antigravity loader accepts both a wrapped dictionary and the older + // direct server map. Keep the existing format instead of shadowing it. + if (provider === "antigravity" && value.mcpServers === undefined && Object.keys(value).length) return { v2: false, keys: [], entries: value }; + if (provider === "muse" && value.mcp_servers !== undefined && value.mcpServers !== undefined) throw problem("unsupported", "Muse has both mcpServers and mcp_servers. Keep one server section before changing servers."); + const key = provider === "opencode" ? "mcp" : provider === "muse" && value.mcp_servers !== undefined ? "mcp_servers" : "mcpServers"; const root = value[key]; if (root !== undefined && !object(root)) throw problem("unsupported", "The MCP configuration has an unsupported server section."); // V1 may contain a real server named 'servers'. Its transport distinguishes @@ -101,16 +128,21 @@ async function fileFor(io, provider, home, env) { if (provider === "codex") return codexMcpPath(home, env); if (provider === "claude") return claudeMcpPath(home, env); if (provider === "cursor") return cursorMcpPath(home, env); + const roots = agentResourcePaths(homeOf(home, env), env); + if (provider === "grok") return path.join(roots.grok, "config.toml"); + if (provider === "muse") return path.join(roots.muse, "settings.json"); + if (provider === "pi") return path.join(roots.pi, "mcp.json"); + if (provider === "antigravity") return path.join(roots.antigravity, "mcp_config.json"); throw problem("unsupported", "MCP management is not supported for this provider."); } // Unknown provider options must be shown as differing, rather than implying // that cwd/auth/tool-policy differences are represented by the shared entry. export function extraMcpOptions(provider, entry, v2 = false) { - const remote = typeof entry?.url === "string"; + const remote = typeof entry?.url === "string" || typeof entry?.serverUrl === "string"; const keys = provider === "codex" ? [...(remote ? ["url", "http_headers"] : ["command", "args", "env"]), "enabled"] : provider === "opencode" ? [...(remote ? ["url", "headers"] : ["command", "environment"]), "type", v2 ? "disabled" : "enabled"] - : [...(remote ? ["url", "headers"] : ["command", "args", "env"]), "type"]; + : [...(remote ? ["url", "headers", ...(provider === "antigravity" ? ["serverUrl"] : [])] : ["command", "args", "env"]), "type", ...(["grok", "muse", "pi"].includes(provider) ? ["enabled"] : provider === "antigravity" ? ["disabled"] : [])]; return object(entry) ? Object.keys(entry).filter((k) => !keys.includes(k)) : []; } export const hasExtraMcpOptions = (provider, entry, v2 = false) => !object(entry) || extraMcpOptions(provider, entry, v2).length > 0; @@ -119,15 +151,19 @@ export async function readProviderMcp(io, provider, { home, env = {} }) { const file = await fileFor(io, provider, home, env); const snapshot = await readConfig(io, file); let entries, v2 = false; - if (provider === "codex") entries = [...parseMcpToml(snapshot.content)]; + if (["codex", "grok"].includes(provider)) entries = [...parseMcpToml(snapshot.content)]; else { const container = containerFor(provider, jsonDocument(snapshot.content, provider !== "opencode")); entries = Object.entries(container.entries); v2 = container.v2; } - const from = provider === "codex" ? fromCodexEntry : provider === "opencode" ? (name, entry) => fromOpenCodeEntry(name, entry, v2) : provider === "cursor" ? fromCursorEntry : fromClaudeEntry; + const from = provider === "codex" ? fromCodexEntry : provider === "opencode" ? (name, entry) => fromOpenCodeEntry(name, entry, v2) : provider === "cursor" ? fromCursorEntry : provider === "claude" ? fromClaudeEntry : (name, entry) => fromAdditionalEntry(provider, name, entry); + // Grok keeps a personal disable list in addition to per-entry switches. + const disabled = provider === "grok" ? parseToml(snapshot.content || "").disabled_mcp_servers ?? [] : []; + if (!Array.isArray(disabled) || !disabled.every(name => typeof name === "string")) throw problem("unsupported", "Grok disabled_mcp_servers must be a list of names."); return { provider, path: file, present: snapshot.exists, revision: snapshot.revision, servers: entries.map(([name, entry]) => { const canonical = from(name, entry); + if (canonical && disabled.includes(name)) canonical.enabled = false; const malformed = !object(entry) || ['enabled', 'disabled'].some(k => entry[k] !== undefined && typeof entry[k] !== 'boolean') || (entry.args !== undefined && (!Array.isArray(entry.args) || !entry.args.every(a => typeof a === 'string'))) @@ -144,8 +180,22 @@ export async function writeProviderMcpEntry(io, provider, { home, env = {} }, na if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) throw problem("invalid", "Invalid MCP server name."); const file = await fileFor(io, provider, home, env); return changeConfig(io, file, revision, (raw) => { - if (provider === "codex") return applyMcpToml(raw, name, entry); + if (["codex", "grok"].includes(provider)) { + if (provider === "grok" && !/^[a-z]/.test(name)) throw problem("unsupported", "Grok Build server names must start with a letter."); + let next = applyMcpToml(raw, name, entry); + if (provider === "grok") { + const value = parseToml(next), disabled = value.disabled_mcp_servers; + if (disabled !== undefined) { + if (!Array.isArray(disabled) || !disabled.every(name => typeof name === "string")) throw problem("unsupported", "Grok disabled_mcp_servers must be a list of names."); + // An explicit on/off save owns this server's personal state only. + next = patchToml(next, { ...value, disabled_mcp_servers: disabled.filter(item => item !== name) }); + } + } + return next; + } + if (provider === "pi" && name === "t3-code") throw problem("unsupported", "The t3-code server is owned by T3. Choose a different server name."); const value = jsonDocument(raw, provider !== "opencode"); + if (provider === "muse" && value.schema_version !== undefined && value.schema_version !== 1) throw problem("unsupported", "This Muse settings schema is not supported. Existing settings were preserved."); const container = containerFor(provider, value); let nextEntry = entry; if (entry && container.v2) { @@ -154,7 +204,9 @@ export async function writeProviderMcpEntry(io, provider, { home, env = {} }, na } if (!entry && !Object.hasOwn(container.entries, name)) return raw; const eol = (raw || "").includes("\r\n") ? "\r\n" : "\n"; - return applyEdits(raw || "{}\n", modify(raw || "{}\n", [...container.keys, name], nextEntry ?? undefined, - { formattingOptions: { insertSpaces: true, tabSize: 2, eol } })); + const formattingOptions = { insertSpaces: true, tabSize: 2, eol }; + let text = raw || "{}\n"; + if (provider === "muse" && value.schema_version === undefined) text = applyEdits(text, modify(text, ["schema_version"], 1, { formattingOptions })); + return applyEdits(text, modify(text, [...container.keys, name], nextEntry ?? undefined, { formattingOptions })); }); } diff --git a/docker/setup/mcp/parse.mjs b/docker/setup/mcp/parse.mjs index ae7d5dbf..b212ea7b 100644 --- a/docker/setup/mcp/parse.mjs +++ b/docker/setup/mcp/parse.mjs @@ -1,7 +1,7 @@ // Import data, never shell code. All formats converge on the same validator. import Command from '../client/mcp-command.js'; import { parseMcpToml } from './toml.mjs'; -import { jsonDocument, containerFor, fromCursorEntry, fromCodexEntry, fromOpenCodeEntry, extraMcpOptions } from './adapters.mjs'; +import { jsonDocument, containerFor, fromCursorEntry, fromCodexEntry, fromOpenCodeEntry, fromAdditionalEntry, extraMcpOptions } from './adapters.mjs'; import { validateMcpServer } from './store.mjs'; export const SSE_MESSAGE = 'SSE-only servers aren’t portable. Use the server’s Streamable HTTP address.'; @@ -85,27 +85,40 @@ export function parseMcpSnippet(text) { } } catch (error) { result.errors.push((label ? label + ': ' : '') + (error instanceof TypeError ? 'Use a valid HTTP server URL.' : error.message)); } }; + const jsonEntry = (name, entry, provider) => { + provider ||= entry?.serverUrl !== undefined || entry?.disabled !== undefined ? 'antigravity' + : entry?.enabled !== undefined || entry?.type === 'streamable-http' ? 'pi' : 'cursor'; + add(provider === 'cursor' ? fromCursorEntry(name, entry) : fromAdditionalEntry(provider, name, entry), name, entry, provider); + }; try { if (/^\s*\[\s*mcp_servers[.\s]/m.test(text)) { result.format = 'codex-toml'; let entries; try { entries = parseMcpToml(text); } catch { fail('Couldn’t read that as Codex TOML. Check that it’s complete.'); } if (entries.size > 128) fail('Import at most 128 servers at a time.'); - for (const [name, entry] of entries) add(fromCodexEntry(name, entry), name, entry, 'codex'); + for (const [name, entry] of entries) { + const grok = entry.headers !== undefined; + add(grok ? fromAdditionalEntry('grok', name, entry) : fromCodexEntry(name, entry), name, entry, grok ? 'grok' : 'codex'); + } } else if (/^[{[]/.test(text)) { result.format = 'json'; let value; try { value = jsonDocument(text, false); } catch { fail('Couldn’t read that as JSON. Check that it’s complete.'); } - if (object(value.mcpServers)) { - result.format = 'claude-json'; - if (Object.keys(value.mcpServers).length > 128) fail('Import at most 128 servers at a time.'); - for (const [name, entry] of Object.entries(value.mcpServers)) add(fromCursorEntry(name, entry), name, entry, 'cursor'); + if (value.mcpServers !== undefined || value.mcp_servers !== undefined) { + const muse = value.mcp_servers !== undefined; + result.format = muse ? 'muse-json' : 'claude-json'; + const { entries } = containerFor('muse', value); + if (Object.keys(entries).length > 128) fail('Import at most 128 servers at a time.'); + for (const [name, entry] of Object.entries(entries)) jsonEntry(name, entry, muse ? 'muse' : undefined); } else if (object(value.mcp)) { result.format = 'opencode-json'; const { entries, v2 } = containerFor('opencode', value); if (Object.keys(entries).length > 128) fail('Import at most 128 servers at a time.'); for (const [name, entry] of Object.entries(entries)) add(fromOpenCodeEntry(name, entry, v2), name, entry, 'opencode', v2); + } else if (Object.keys(value).length && Object.values(value).every(object)) { + if (Object.keys(value).length > 128) fail('Import at most 128 servers at a time.'); + for (const [name, entry] of Object.entries(value)) jsonEntry(name, entry); } else { - const name = suggestMcpName(value.url || [value.command || 'server', ...(Array.isArray(value.args) ? value.args : [])]); - add(fromCursorEntry(name, value), '', value, 'cursor'); + const name = suggestMcpName(value.serverUrl || value.url || [value.command || 'server', ...(Array.isArray(value.args) ? value.args : [])]); + jsonEntry(name, value); } } else if (/^https?:\/\/\S+$/.test(text)) { result.format = 'url'; add({ name: suggestMcpName(text), type: 'remote', url: text, headers: {} }); diff --git a/docker/setup/mcp/store.mjs b/docker/setup/mcp/store.mjs index 46f785ef..282af571 100644 --- a/docker/setup/mcp/store.mjs +++ b/docker/setup/mcp/store.mjs @@ -1,21 +1,12 @@ // Shared MCP server store. Canonical JSON on the state volume; provider // files are only touched by explicit link/unlink saves. import path from "node:path"; +import { AGENT_NAMES } from "../agent-resources.mjs"; import { createHash, randomBytes } from "node:crypto"; -export const MCP_PROVIDERS = Object.freeze({ - claude: "Claude Code", - codex: "Codex", - opencode: "OpenCode", - cursor: "Cursor", -}); +export const MCP_PROVIDERS = AGENT_NAMES; export const SUPPORTED_MCP_PROVIDERS = Object.freeze(Object.keys(MCP_PROVIDERS)); -// Keep native formats without a verified integration explicitly unavailable. -export const UNSUPPORTED_MCP_PROVIDERS = Object.freeze({ - antigravity: "Antigravity uses isolated T3 profiles; shared MCP settings are not integrated yet.", - grok: "Grok MCP management is not implemented. Grok may also load Claude and Cursor MCP settings through its native compatibility imports.", - muse: "Muse MCP management is not implemented yet.", -}); +export const UNSUPPORTED_MCP_PROVIDERS = Object.freeze({}); const MAX_STORE_BYTES = 256 * 1024; const MAX_SERVERS = 128; diff --git a/docker/setup/mcp/toml.mjs b/docker/setup/mcp/toml.mjs index 510dc231..0eb0092a 100644 --- a/docker/setup/mcp/toml.mjs +++ b/docker/setup/mcp/toml.mjs @@ -3,7 +3,7 @@ import { parse, patch, stringify, TomlDocument } from "../vendor/toml-patch.mjs"; import { isDeepStrictEqual } from "node:util"; -const problem = () => Object.assign(new Error("Codex config.toml cannot be safely edited. Check its TOML syntax before changing MCP servers."), { code: "unsupported" }); +const problem = () => Object.assign(new Error("The native config.toml cannot be safely edited. Check its TOML syntax before changing MCP servers."), { code: "unsupported" }); const object = (v) => v && typeof v === "object" && !Array.isArray(v); const plain = (v) => Array.isArray(v) ? v.map(plain) : v && Object.getPrototypeOf(v) === null ? Object.fromEntries(Object.entries(v).map(([k, x]) => [k, plain(x)])) : v; diff --git a/docker/setup/server.mjs b/docker/setup/server.mjs index 44efc10f..b73ec4fe 100644 --- a/docker/setup/server.mjs +++ b/docker/setup/server.mjs @@ -597,9 +597,12 @@ const loadStorage = () => { const CONTEXT_HOME = process.env.HOME || "/home/t3"; const contextFs = async () => (await import("node:fs/promises")); const skillsService = createSkillsService({ home: CONTEXT_HOME, env: process.env, + onNativeChange: provider => gateways.syncNativeResources(provider), getHarness: async (provider) => (await harnessCache.snapshotCheap()).harnesses?.find((row) => row.id === provider), }); const mcpService = createMcpService({ home: CONTEXT_HOME, env: process.env, baseDir: STATE_DIR, + getHarness: async provider => (await harnessCache.snapshotCheap()).harnesses?.find(row => row.id === provider), + onNativeChange: provider => gateways.syncNativeResources(provider), io: await contextFs(), }); let credentialInstalled = null; diff --git a/docker/setup/skills.mjs b/docker/setup/skills.mjs index 0c2ca060..07e896fa 100644 --- a/docker/setup/skills.mjs +++ b/docker/setup/skills.mjs @@ -13,9 +13,12 @@ import { adapterDetection } from "./skills/adapters.mjs"; import { resolveSkillSource, parseSkillSource } from "./skills/sources.mjs"; import { createSkillImporter } from "./skills/imports.mjs"; import { skillReach } from "./skills/reach.mjs"; +import { createSkillPlacement, placementProviders, disabledSkillRoot } from "./skills/placement.mjs"; +import { createAdditionalSkillPolicies } from "./skills/additional-policies.mjs"; +import { AGENT_NAMES, agentResourcePaths } from "./agent-resources.mjs"; import { createPluginHookService } from "./skills/hooks.mjs"; -const PROVIDERS = Object.freeze({ claude: "Claude Code", codex: "Codex", opencode: "OpenCode", cursor: "Cursor", grok: "Grok Build", muse: "Muse Code", antigravity: "Antigravity" }); +const PROVIDERS = AGENT_NAMES; const DEFAULT_LIMITS = { entries: 2048, skills: 256, depth: 8, bytes: 4 * 1024 * 1024, fileBytes: 64 * 1024, milliseconds: 3000 }; const problem = (code, message) => Object.assign(new Error(message), { code }); const summaryError = (error) => String(error?.message || error).slice(0, 240); @@ -27,6 +30,7 @@ export function skillRoots(provider, { home, env = {} }) { if (!Object.hasOwn(PROVIDERS, provider)) throw problem("invalid", "Unknown skill provider."); const override = (key, fallback) => path.resolve(home, String(env[key] || "").trim() || fallback); const claude = override("CLAUDE_CONFIG_DIR", ".claude"), codex = override("CODEX_HOME", ".codex"); + const resources = agentResourcePaths(home, env); const config = override("XDG_CONFIG_HOME", ".config"), shared = path.join(home, ".agents/skills"); const own = (directory) => ({ path: path.join(directory, "skills"), source: "Provider", scope: "user" }); const compat = (directory, source) => ({ path: directory, source, scope: "user" }); @@ -35,9 +39,10 @@ export function skillRoots(provider, { home, env = {} }) { claude: [own(claude)], codex: [own(codex), common, { path: path.join(codex, "skills/.system"), source: "System", scope: "system" }, { path: "/etc/codex/skills", source: "Admin", scope: "admin" }], opencode: [own(override("OPENCODE_CONFIG_DIR", path.join(config, "opencode"))), compat(path.join(claude, "skills"), "Claude compatibility"), common], - cursor: [own(path.join(home, ".cursor")), common, compat(path.join(claude, "skills"), "Claude compatibility"), compat(path.join(codex, "skills"), "Codex compatibility")], - grok: [own(override("GROK_HOME", ".grok")), compat(path.join(claude, "skills"), "Claude compatibility"), common, compat(path.join(home, ".cursor/skills"), "Cursor compatibility")], - muse: [own(path.join(config, "muse")), common, compat(path.join(claude, "skills"), "Claude compatibility"), compat(path.join(codex, "skills"), "Codex compatibility")], + cursor: [own(resources.cursor), common, compat(path.join(claude, "skills"), "Claude compatibility"), compat(path.join(codex, "skills"), "Codex compatibility")], + grok: [own(override("GROK_HOME", ".grok")), compat(path.join(claude, "skills"), "Claude compatibility"), common, compat(path.join(resources.cursor, "skills"), "Cursor compatibility")], + muse: [own(resources.muse), common, compat(path.join(home, ".claude/skills"), "Claude compatibility"), compat(path.join(codex, "skills"), "Codex compatibility")], + pi: [own(resources.pi), common], antigravity: [own(path.join(home, ".gemini/config")), compat(path.join(home, ".gemini/antigravity-cli/skills"), "Antigravity CLI")], }[provider]; return roots.filter((root, index) => roots.findIndex((other) => other.path === root.path) === index); @@ -146,13 +151,16 @@ export async function scanSkillFiles(provider, options) { } }; for (const root of roots) await walk(root.path, root, 0, new Set()); + if (placementProviders.includes(provider)) await walk(disabledSkillRoot(roots[0].path), { path: disabledSkillRoot(roots[0].path), source: "Parked provider skill", scope: "user" }, 0, new Set()); return { skills: [...rows.values()], roots, warnings: [...warnings] }; } -export function createSkillsService({ home, env = process.env, io = fs, nativeCodex = listCodexSkills, nativeCodexWrite = writeCodexSkillConfig, nativeCodexHooks, nativeCodexHookWrite, inspectClaude, inspectOpenCode, getHarness = async () => null, ttlMs = 15000, limits, resolveSource } = {}) { +export function createSkillsService({ home, env = process.env, io = fs, nativeCodex = listCodexSkills, nativeCodexWrite = writeCodexSkillConfig, nativeCodexHooks, nativeCodexHookWrite, inspectClaude, inspectOpenCode, getHarness = async () => null, onNativeChange = async () => {}, ttlMs = 15000, limits, resolveSource } = {}) { home = path.resolve(home || env.HOME || "/home/t3"); const cache = new Map(), updateStates = new Map(), updateChecks = new Map(); const policies = createSkillPolicies({ home, env, io, getHarness, inspectClaude, inspectOpenCode, invalidate: () => cache.clear() }); + const placement = createSkillPlacement({ io, readSkillFile, invalidate: () => cache.clear() }); + const additionalPolicies = createAdditionalSkillPolicies({ home, env, io, invalidate: () => cache.clear(), onNativeChange }); const load = async (provider) => { const scanned = await scanSkillFiles(provider, { home, env, io, limits }); let coverage = "files", nativeError = null, config = null; @@ -199,7 +207,17 @@ export function createSkillsService({ home, env = process.env, io = fs, nativeCo scanned.skills = result.skills; coverage = "policy"; } catch (error) { nativeError = summaryError(error); } } + if (["grok", "muse", "pi"].includes(provider)) { + try { + const result = await additionalPolicies.inspect(provider, scanned.skills); + scanned.skills = result.skills; coverage = "settings"; + } catch (error) { nativeError = summaryError(error); } + } const policy = await fileManager.policyFor(provider); + if (placementProviders.includes(provider)) { + scanned.skills = await placement.inspect(scanned.roots[0].path, scanned.skills, policy); + coverage = "placement"; + } const adapter = await adapterDetection(provider, { home, env, io }); for (const row of scanned.skills) { const origin = await readSkillOrigin(io, row, home); @@ -210,7 +228,7 @@ export function createSkillsService({ home, env = process.env, io = fs, nativeCo } } return { ok: true, provider, canCreate: policy.canCreate, creationTargets: policy.creationTargets, providers: skillProviders(), skills: scanned.skills.map((row) => { - if (row.stateAuthority === "user-policy") return { ...row, ...policy.capabilities(row) }; + if (["user-policy", "placement"].includes(row.stateAuthority)) return { ...row, ...policy.capabilities(row) }; const reason = provider !== "codex" ? nativeError || "Native policy controls are currently supported for Claude Code and OpenCode; native availability controls for Codex." : !row.native ? "Codex has not verified this skill's availability." : row.scope !== "user" || row.source === "Plugin" ? "System, admin and plugin availability is managed through its source." @@ -220,13 +238,17 @@ export function createSkillsService({ home, env = process.env, io = fs, nativeCo stateRevision: !reason ? stateRevision(config, row) : null }; }).sort((a, b) => a.name.localeCompare(b.name) || a.path.localeCompare(b.path)), roots: scanned.roots, coverage, nativeError, warnings: scanned.warnings, scannedAt: Date.now(), adapter, - coverageText: coverage === "policy" ? "Known global skill folders with native user policy settings checked by the managed provider. Project/session and managed policies can further restrict availability." + coverageText: coverage === "placement" ? "Known global folders. On/off parks ordinary local installations outside discovery; native activation, plugins and session state are not reported." + : coverage === "settings" ? "Known global folders with native user skill settings. Project, package and session overrides are not verified." + : coverage === "policy" ? "Known global skill folders with native user policy settings checked by the managed provider. Project/session and managed policies can further restrict availability." : coverage === "native" ? "Managed Codex inventory in the setup environment, with known local folders included. Project skills are excluded." : "Known global skill folders only. Provider availability, plugins and additional configured locations are not verified.", }; }; const setState = async (provider, id, revision, enabled, state) => { if (!Object.hasOwn(PROVIDERS, provider) || typeof id !== "string" || !/^[a-f0-9]{32}$/.test(id)) throw problem("invalid", "Expected a provider and skill identifier."); + if (placementProviders.includes(provider)) return placement.set(provider, () => load(provider), id, revision, enabled); + if (["grok", "muse", "pi"].includes(provider)) return additionalPolicies.set(provider, () => load(provider), id, revision, state); if (["claude", "opencode"].includes(provider)) return policies.set(provider, () => load(provider), id, revision, state); if (typeof enabled !== "boolean") throw problem("invalid", "Expected a boolean enabled value."); if (provider !== "codex") throw problem("unsupported", "Native availability controls are currently supported for Codex."); diff --git a/docker/setup/skills/adapters.mjs b/docker/setup/skills/adapters.mjs index 59fba592..b5c70a45 100644 --- a/docker/setup/skills/adapters.mjs +++ b/docker/setup/skills/adapters.mjs @@ -5,6 +5,7 @@ // Detection is advisory only: an undetected tool stays fully manageable // through its files, unlike registries that refuse unknown tools. import path from "node:path"; +import { agentResourcePaths } from "../agent-resources.mjs"; const fail = (code, message) => Object.assign(new Error(message), { code }); @@ -17,8 +18,9 @@ const TABLE = Object.freeze({ codex: Object.freeze({ label: "Codex", projectDir: ".agents/skills" }), opencode: Object.freeze({ label: "OpenCode", projectDir: ".agents/skills" }), cursor: Object.freeze({ label: "Cursor", projectDir: ".agents/skills" }), - grok: Object.freeze({ label: "Grok Build", projectDir: null }), - muse: Object.freeze({ label: "Muse Code", projectDir: null }), + grok: Object.freeze({ label: "Grok Build", projectDir: ".grok/skills" }), + muse: Object.freeze({ label: "Muse Code", projectDir: ".agents/skills" }), + pi: Object.freeze({ label: "Pi", projectDir: ".pi/skills" }), antigravity: Object.freeze({ label: "Antigravity", projectDir: ".agents/skills" }), }); @@ -31,17 +33,7 @@ export function adapterProviders() { export function skillAdapter(provider, { home, env = {} } = {}) { const entry = TABLE[provider]; if (!entry) throw fail("invalid", "Unknown skill provider."); - const override = (key, fallback) => path.resolve(home, String(env[key] || "").trim() || fallback); - const config = override("XDG_CONFIG_HOME", ".config"); - const root = { - claude: override("CLAUDE_CONFIG_DIR", ".claude"), - codex: override("CODEX_HOME", ".codex"), - opencode: override("OPENCODE_CONFIG_DIR", path.join(config, "opencode")), - cursor: path.join(home, ".cursor"), - grok: override("GROK_HOME", ".grok"), - muse: path.join(config, "muse"), - antigravity: path.join(home, ".gemini", "config"), - }[provider]; + const root = agentResourcePaths(home, env)[provider]; return { id: provider, label: entry.label, diff --git a/docker/setup/skills/additional-policies.mjs b/docker/setup/skills/additional-policies.mjs new file mode 100644 index 00000000..eb4df6d0 --- /dev/null +++ b/docker/setup/skills/additional-policies.mjs @@ -0,0 +1,100 @@ +// Native user policies for Grok, Muse and Pi. These describe saved user +// settings; project, package and managed policies may further restrict them. +import path from 'node:path'; +import { parse, patch } from '../vendor/toml-patch.mjs'; +import { parseSettings } from './policies.mjs'; +import { readConfig, changeConfig } from '../mcp/files.mjs'; +import { modify, applyEdits } from '../vendor/jsonc.mjs'; +import { stateRevision } from './state.mjs'; +import { agentResourcePaths } from '../agent-resources.mjs'; +const fail = (code, message) => Object.assign(new Error(message), { code }); +const object = value => value && typeof value === 'object' && !Array.isArray(value); +const list = value => value === undefined ? [] : Array.isArray(value) && value.every(item => typeof item === 'string') ? value : (() => { throw fail('unsupported', 'Native skill settings must contain a list of paths or names.'); })(); +const optionsFor = provider => (provider === 'muse' ? ['on', 'user-invocable-only', 'off'] : ['enabled', 'disabled']).map(value => ({ value, label: ({ on: 'On', 'user-invocable-only': 'Manual only', off: 'Off', enabled: 'On', disabled: 'Off' })[value] })); + +export function createAdditionalSkillPolicies({ home, env, io, invalidate, onNativeChange = async () => {} }) { + const roots = agentResourcePaths(home, env); + const fileFor = provider => path.join(roots[provider], provider === 'grok' ? 'config.toml' : 'settings.json'); + // Muse persists the same symbolic display path that `skills list` returns, + // including for linked installations. Absolute paths are different keys. + const museKey = file => { + const prefixes = [[roots.muse, '$CONFIG_DIR'], ...(path.isAbsolute(env.CODEX_HOME || '') ? [[env.CODEX_HOME, '$CODEX_HOME']] : []), [home, '$HOME']]; + for (const [root, label] of prefixes) { + const relative = path.relative(root, file); + if (relative && relative !== '..' && !relative.startsWith('../') && !path.isAbsolute(relative)) return label + '/' + relative.split(path.sep).join('/'); + } + return file; + }; + function document(provider, content) { + if (provider === 'grok') { + let value; + try { value = parse(content); } catch { throw fail('unsupported', 'Grok settings are not valid TOML. Repair them before changing skill settings.'); } + if (value.skills !== undefined && !object(value.skills)) throw fail('unsupported', 'Grok skills must be a table.'); + return value; + } + const value = parseSettings(content || '{}', true); + if (provider === 'muse' && value.schema_version !== undefined && value.schema_version !== 1) throw fail('unsupported', 'This Muse settings schema is not supported.'); + if (provider === 'muse' && (value.skills !== undefined && !object(value.skills) + || value.skills?.activation !== undefined && !object(value.skills.activation))) throw fail('unsupported', 'Muse skill activation settings must be objects.'); + return value; + } + function stateFor(provider, config, row) { + if (provider === 'grok') return list(config.skills?.disabled).includes(row.name) ? 'disabled' : 'enabled'; + if (provider === 'muse') { + const activation = config.skills?.activation?.user; + if (activation !== undefined && !object(activation)) throw fail('unsupported', 'Muse user skill activation must be an object.'); + return activation?.[museKey(row.path)] ?? (row.invocation === 'manual' ? 'user-invocable-only' : 'on'); + } + // Exact Pi overrides take precedence over glob/package exclusions. Keep + // arbitrary globs untouched and call this a user setting, not effective state. + const exact = list(config.skills).filter(value => ['+', '-'].includes(value[0]) && path.resolve(roots.pi, value.slice(1)) === row.path).at(-1); + return exact?.startsWith('-') ? 'disabled' : 'enabled'; + } + const inspect = async (provider, skills) => { + const file = fileFor(provider), snapshot = await readConfig(io, file), config = document(provider, snapshot.content); + return { skills: skills.map(row => { + const state = stateFor(provider, config, row), options = optionsFor(provider); + const reason = row.error || row.metadataError || (row.scope !== 'user' ? 'Manage system skills through their source.' : null) + || (!options.some(option => option.value === state) ? 'This native skill state is not supported here.' : null); + const result = { ...row, state, stateAuthority: 'user-policy', stateLabel: 'User setting: ' + (options.find(option => option.value === state)?.label || 'Unknown'), + statePath: file, policyKey: provider === 'muse' ? museKey(row.path) : provider === 'pi' ? row.path : row.name, + canSetState: !reason, canToggle: false, stateOptions: options, stateReadOnlyReason: reason, + effectiveStateText: 'Saved user setting. Native project, package and session settings can further restrict availability.' }; + return { ...result, stateRevision: !reason ? stateRevision(snapshot.revision, result) : null }; + }) }; + }; + const set = async (provider, skillsFor, id, revision, state) => { + if (!optionsFor(provider).some(option => option.value === state)) throw fail('invalid', 'Unknown native skill state.'); + const inventory = await skillsFor(), row = inventory.skills.find(row => row.id === id); + if (!row) throw fail('missing', 'This skill is no longer in the inventory.'); + if (!row.canSetState) throw fail('unsupported', row.stateReadOnlyReason); + if (!revision || row.stateRevision !== revision) throw fail('conflict', 'Native settings or this skill changed. Refresh before trying again.'); + const file = fileFor(provider), before = await readConfig(io, file); + if (stateRevision(before.revision, row) !== revision || await io.realpath(row.path).catch(() => null) !== row.resolvedPath) throw fail('conflict', 'Native settings or the skill link changed. Refresh before trying again.'); + await changeConfig(io, file, before.revision, content => { + const config = document(provider, content); + if (provider === 'grok') { + if (config.skills !== undefined && !object(config.skills)) throw fail('unsupported', 'Grok skills must be a table.'); + const disabled = list(config.skills?.disabled).filter(name => name !== row.name); + if (state === 'disabled') disabled.push(row.name); + return patch(content, { ...config, skills: { ...config.skills, disabled } }); + } + let text = content || '{}\n'; + const formattingOptions = { insertSpaces: true, tabSize: 2, eol: text.includes('\r\n') ? '\r\n' : '\n' }; + const edit = (keys, value) => { text = applyEdits(text, modify(text, keys, value, { formattingOptions })); }; + if (provider === 'muse') { + if (config.schema_version === undefined) edit(['schema_version'], 1); + edit(['skills', 'activation', 'user', museKey(row.path)], state); + } else { + const entries = list(config.skills).filter(value => !(['+', '-'].includes(value[0]) && path.resolve(roots.pi, value.slice(1)) === row.path)); + entries.push((state === 'disabled' ? '-' : '+') + row.path); + edit(['skills'], entries); + } + return text; + }); + invalidate(); + const propagation = await onNativeChange(provider); + return { ok: true, provider, id, state, ...(propagation ? { propagation } : {}) }; + }; + return { inspect, set }; +} diff --git a/docker/setup/skills/files.mjs b/docker/setup/skills/files.mjs index b130094e..8ac9bc61 100644 --- a/docker/setup/skills/files.mjs +++ b/docker/setup/skills/files.mjs @@ -3,7 +3,7 @@ import path from "node:path"; import { createHash, randomBytes } from "node:crypto"; import { constants } from "node:fs"; -const writableProviders = new Set(["claude", "codex", "opencode", "cursor", "grok", "muse", "antigravity"]); +const writableProviders = new Set(["claude", "codex", "opencode", "cursor", "grok", "muse", "antigravity", "pi"]); const fail = (code, message) => Object.assign(new Error(message), { code }); const inside = (parent, file) => { const relative = path.relative(parent, file); return relative !== "" && !relative.startsWith(".." + path.sep) && relative !== ".." && !path.isAbsolute(relative); }; const stamp = (stat) => [stat.dev, stat.ino, stat.mode, stat.size, stat.mtimeMs].join(":"); diff --git a/docker/setup/skills/imports.mjs b/docker/setup/skills/imports.mjs index 445ae7a1..37e0754c 100644 --- a/docker/setup/skills/imports.mjs +++ b/docker/setup/skills/imports.mjs @@ -1,5 +1,6 @@ // Previewed source snapshots become local skills with repository provenance. import path from "node:path"; +import { adapterProviders } from "./adapters.mjs"; import { randomBytes } from "node:crypto"; import { resolveSkillSource } from "./sources.mjs"; import { ORIGIN_FILE, skillFingerprint, readSkillOrigin } from "./origins.mjs"; @@ -91,7 +92,7 @@ export function createSkillImporter({ home, env, io, metadata, policyFor, invali const partial = snapshot.result?.results.some(entry => entry.status === "failed"); if (snapshot.installing || snapshot.planning || (snapshot.result && !partial)) throw fail("conflict", "This preview has already been installed. Preview again to make another change."); if (!Array.isArray(names) || !names.length || names.length > 128 || new Set(names).size !== names.length || names.some((name) => !snapshot.skills.some((skill) => skill.name === name))) throw fail("invalid", "Choose skills from this preview."); - if (!Array.isArray(providers) || !providers.length || providers.length > 7 || new Set(providers).size !== providers.length) throw fail("invalid", "Choose at least one provider."); + if (!Array.isArray(providers) || !providers.length || providers.length > adapterProviders().length || new Set(providers).size !== providers.length) throw fail("invalid", "Choose at least one provider."); snapshot.planning = true; try { const destinations = [], physical = new Map(); diff --git a/docker/setup/skills/placement.mjs b/docker/setup/skills/placement.mjs new file mode 100644 index 00000000..6614f4a5 --- /dev/null +++ b/docker/setup/skills/placement.mjs @@ -0,0 +1,73 @@ +// Agents without a native per-skill switch can stop discovering an ordinary +// local installation by parking its whole directory outside the skills root. +// Supporting files and directory links move intact; shared/package skills do +// not get an inferred per-agent policy. +import path from 'node:path'; +import { createHash } from 'node:crypto'; +import { serialized, skillRevision } from './files.mjs'; +const fail = (code, message) => Object.assign(new Error(message), { code }); +export const placementProviders = ['cursor', 'antigravity']; +export const disabledSkillRoot = root => root + '.t3-disabled'; +export function placementPaths(root, row) { + const parked = disabledSkillRoot(root), disabled = row.path.startsWith(parked + path.sep); + const base = disabled ? parked : root; + const relative = path.relative(base, row.path), parts = relative.split(path.sep); + if (parts.length !== 2 || parts[1] !== 'SKILL.md' || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(parts[0])) return null; + return { disabled, source: path.join(base, parts[0]), target: path.join(disabled ? root : parked, parts[0]) }; +} +const digest = value => createHash('sha256').update(JSON.stringify(value)).digest('hex'); +export function createSkillPlacement({ io, readSkillFile, invalidate }) { + const revisionFor = async (root, row) => { + const paths = placementPaths(root, row); + if (!paths) return null; + const file = await readSkillFile(row.resolvedPath, 65536, io), stat = await io.lstat(paths.source); + const occupied = await io.lstat(paths.target).then(() => true, error => { if (error.code !== 'ENOENT') throw error; return false; }); + return digest([skillRevision(row, file), stat.dev, stat.ino, stat.mtimeMs, occupied]); + }; + const inspect = async (root, skills, policy) => Promise.all(skills.map(async row => { + const paths = placementPaths(root, row); + if (!paths) return row; + // Disabled files are intentionally outside the normal editable roots. + const active = paths.disabled ? { ...row, path: path.join(root, path.basename(paths.source), 'SKILL.md') } : row; + let reason = policy.capabilities(active).readOnlyReason; + if (skills.some(other => other.id !== row.id && other.name === row.name && !other.path.startsWith(disabledSkillRoot(root) + path.sep))) reason ||= 'Another installation has this name. Resolve the duplicate before moving this installation.'; + const result = { ...row, placementKey: path.join(root, path.basename(paths.source), 'SKILL.md'), + state: paths.disabled ? 'disabled' : 'enabled', stateAuthority: 'placement', canToggle: !reason, canSetState: false, + stateReadOnlyReason: reason, stateLabel: paths.disabled ? 'Parked outside discovery' : 'In provider folder', + effectiveStateText: 'On/off moves this local installation, with its supporting files, into or out of the provider folder. Other agents reading this folder see the same move.' }; + return { ...result, stateRevision: !reason ? await revisionFor(root, row) : null }; + })); + const set = async (provider, skillsFor, id, revision, enabled) => { + if (typeof enabled !== 'boolean') throw fail('invalid', 'Expected a boolean availability choice.'); + const first = await skillsFor(), root = first.roots[0].path; + return serialized(root, async () => { + const inventory = await skillsFor(), row = inventory.skills.find(row => row.id === id); + if (!row) throw fail('missing', 'This installation is no longer in the inventory.'); + if (!row.canToggle) throw fail('unsupported', row.stateReadOnlyReason || 'This installation cannot be moved.'); + if (!revision || row.stateRevision !== revision || await revisionFor(root, row) !== revision) throw fail('conflict', 'This skill or its destination changed. Refresh before trying again.'); + const locations = placementPaths(root, row); + if (enabled === !locations.disabled) return { ok: true, provider, id, enabled }; + try { await io.lstat(locations.target); throw fail('conflict', 'The destination already exists. Resolve it before moving this skill.'); } + catch (error) { if (error.code !== 'ENOENT') throw error; } + await io.mkdir(path.dirname(locations.target), { recursive: true }); + if (await revisionFor(root, row) !== revision) throw fail('conflict', 'This skill changed before it could be moved. Refresh before trying again.'); + const sourceStat = await io.lstat(locations.source); + if (sourceStat.isSymbolicLink()) { + // Publish exclusively and keep relative links pointing at the same + // installation when their parent changes to the parked root. + const link = await io.readlink(locations.source); + const target = path.isAbsolute(link) ? link : path.relative(path.dirname(locations.target), path.resolve(path.dirname(locations.source), link)); + try { await io.symlink(target, locations.target); } + catch (error) { if (error.code === 'EEXIST') throw fail('conflict', 'The destination already exists. Refresh before moving this skill.'); throw error; } + try { + const current = await io.lstat(locations.source); + if (!current.isSymbolicLink() || current.ino !== sourceStat.ino || current.dev !== sourceStat.dev || await io.readlink(locations.source) !== link) throw fail('conflict', 'The skill link changed before it could be moved.'); + await io.unlink(locations.source); + } catch (error) { await io.unlink(locations.target); throw error; } + } else await io.rename(locations.source, locations.target); + invalidate(); + return { ok: true, provider, id, enabled }; + }); + }; + return { inspect, set }; +} diff --git a/docs/context-management.md b/docs/context-management.md index a878e522..97e27649 100644 --- a/docs/context-management.md +++ b/docs/context-management.md @@ -30,6 +30,22 @@ missing native files: | Claude Code | `~/.claude/CLAUDE.md` | `CLAUDE_CONFIG_DIR` | | Codex | `~/.codex/AGENTS.md` | `CODEX_HOME` | | OpenCode | `$OPENCODE_CONFIG_DIR/AGENTS.md` | `OPENCODE_CONFIG_DIR`; native fallback uses `XDG_CONFIG_HOME` | +| Cursor | `~/.cursor/t3-instructions/AGENTS.md` | Loaded as an additional workspace by T3's Cursor SDK bridge | +| Grok Build | `~/.grok/rules/t3-instructions.md` | `GROK_HOME` | +| Muse Code | `~/.config/muse/AGENTS.md` | `XDG_CONFIG_HOME` | +| Antigravity | `~/.gemini/config/AGENTS.md` | Linked into T3's private `GEMINI_HOME` | +| Pi | `~/.pi/agent/AGENTS.md` | `PI_CODING_AGENT_DIR` | + +Codex and Pi use an existing `AGENTS.override.md` before `AGENTS.md`. Pi also +recognizes existing `AGENTS.MD`, `CLAUDE.md` and `CLAUDE.MD` fallbacks. Setup +edits the file selected by that precedence instead of creating a shadowed file. +Cursor's bridge preserves the primary project, other workspace directories and +the SDK's native system prompt. Its instruction workspace applies to T3 sessions. + + + + Shared and separate instruction choices for all eight agents + The shared file's header says which agents read it. Its actions menu offers upload, download and delete; the preview refreshes when its revision changes or @@ -41,13 +57,13 @@ reads**, each agent has one choice of three: that agent. **Edit** opens the shared editor. Switching an existing own file to it asks for confirmation and backs up its directory entry next to the native file, with the suffix `.t3-context-backup`. Existing backups are never overwritten. -- **Its own** `CLAUDE.md` or `AGENTS.md` uses the agent's own file, with its path +- **Own file** uses the agent's native instruction file, with its path under the agent's name. Switching back restores its previous file, including the original permissions or relative symlink. If there is no previous file, it starts with a copy of the shared instructions; before shared instructions exist, it starts with a Markdown heading. Its editor opens after switching. Later shared edits leave its own file alone. -- **Nothing** removes the native link or parks its own file in its backup slot, +- **No managed global instructions** removes the native link or parks its own file in its backup slot, after asking. Later global edits leave it disconnected; choosing its own file again restores the backup. Deleting the global file disconnects shared providers and keeps backups. @@ -83,14 +99,15 @@ Start a new agent session after saving: an existing session may retain the instructions it loaded at startup. Project instructions and provider-specific precedence still apply. -Only these three native instruction integrations are included. Project -instruction editing, saved setups and Cursor rule translation are outside this page. +All eight agents support global instructions. Project instruction editing, +saved setups and conversion to provider-specific rule formats are outside this page. +Other native rules and project instructions may still apply when no global file is managed here. Skills and source imports are described below. ## Global skill inventory The **Skills** tab is one library across installed Claude Code, Codex, -OpenCode, Cursor, Grok Build, Muse Code and Antigravity agents. Each physical +OpenCode, Cursor, Grok Build, Muse Code, Antigravity and Pi agents. Each physical `SKILL.md` is one row, joined by resolved path, with one column per installed agent. A name appearing in two different folders remains two installations. The **View** menu offers **Unified view · one row per skill name** for the @@ -151,8 +168,10 @@ fall back to the local list with a reason. Custom T3 provider binaries, environments and project/session overrides may produce different inventories. Claude and OpenCode supplement known-folder discovery with native user policies -and runtime metadata, as described below. Cursor, Grok, Muse and Antigravity show -**Found** with a discovery-only explanation. Plugin inventories and configured extra locations +and runtime metadata, as described below. Grok, Muse and Pi expose saved native +user settings. Cursor and Antigravity can park ordinary local installations +outside discovery. These controls do not claim effective session availability. +Plugin inventories and configured extra locations are outside the basic known-folder inventory. **View → Show bundled skills** controls entries identified as system skills, @@ -185,7 +204,7 @@ startup may maintain its own system-skill cache as usual. ### Add, edit and remove local skills -All seven providers' own user skill folders support **New skill**, +All eight providers' own user skill folders support **New skill**, **Edit** and **Remove**. Add accepts a portable folder name and raw Markdown, including upload/paste. The frontmatter name must match the new folder name. Existing installation names are never overwritten; new files use mode `0600`. @@ -278,7 +297,7 @@ The three steps keep one reviewed snapshot throughout: **Review again** reviews a failed destination using the same snapshot, without repeating successful writes. **Show in library** highlights the installed names. -**Fewest copies** checks all subsets of at most seven writable, installed agents' +**Fewest copies** checks all subsets of the writable, installed agents' own folders. It minimizes physical folders, then agents reading duplicate copies, then prefers agents' own folders. With Claude, Codex, OpenCode, Cursor and Grok selected, it uses Claude's and Codex's folders; Cursor can find both identical @@ -291,7 +310,7 @@ the planner retains the handoff's ordering rather than guessing runtime behavior New skills use each agent's defaults; the result does not claim Codex enabled them. The reach map comes from authenticated `GET /context/skills/reach`. It uses the -same seven-provider roots and environment overrides as discovery, joins aliases +same eight-provider roots and environment overrides as discovery, joins aliases by resolved path, and reports readers and writable owning providers. It grants no write authority: the plan/install APIs still validate every destination. Closing discards the snapshot; a late response is discarded too. Snapshots expire @@ -315,6 +334,7 @@ Imports create independent local copies in these native global locations: | Grok Build | `~/.grok/skills` | `GROK_HOME` | | Muse Code | `~/.config/muse/skills` | `XDG_CONFIG_HOME` | | Antigravity | `~/.gemini/config/skills` | — | +| Pi | `~/.pi/agent/skills` | `PI_CODING_AGENT_DIR` | The Antigravity inventory also includes `~/.gemini/antigravity-cli/skills`; T3 links both native roots into its private provider profile. Its CLI folder @@ -494,7 +514,45 @@ filesystem checks; this is not a cross-application transaction. All native default locations, including both scoped Antigravity skills directories, participate in agent persistence when `T3_PERSIST_AGENT_CREDENTIALS` is enabled. -Custom paths require their own mounts. The rest of `.gemini` is left in place. +Antigravity's global config, parked skills and optional standalone instruction +files also persist. Existing scoped skill links are retained during migration. +Custom paths require their own mounts. Unrelated `.gemini` resources stay in place. + +### Grok, Muse and Pi skill settings + +Grok writes the skill name to `[skills].disabled` in `config.toml`. Muse writes +On, Manual only or Off under `skills.activation.user` in `settings.json`, using +native display paths such as `$CONFIG_DIR/skills/name/SKILL.md`. Pi writes exact +`+absolute-path` or `-absolute-path` entries to `settings.json.skills`, preserving +other paths, globs and package settings. Writes preserve unrelated settings, +share the MCP configuration queue and reject stale revisions or retargeted links. +Grok changes also refresh connected gateway profiles without interrupting turns. + +These states report the saved user setting. Project, package, enterprise and +session overrides can affect actual availability. The console does not verify +every configured extra skill path or run a model turn to infer availability. + + + + Muse skill inspector showing its native Manual only user setting + + +### Cursor and Antigravity skill placement + +These agents do not expose a verified native per-skill Off setting. For an +ordinary local installation in the provider's own folder, Off moves its complete +directory into the adjacent `skills.t3-disabled` folder. On restores it. Scripts, +references and permissions are retained; directory links keep their target. +Parked skills remain visible and can be restored, but cannot be edited in place. +An open inspector follows the moved installation, refreshes its path and file +revisions, and preserves unsaved drafts. Discovery is refreshed for other agents +that read the same folder. + +Shared, package, system and plugin installations remain managed through their +source. Duplicate active names must be resolved before moving a copy. A move +affects every agent reading that folder, so the control explains its scope. +Destinations are checked before moving; an occupied destination is a conflict. +Start a new session after changing placement or native settings. ### Native Codex availability @@ -696,7 +754,10 @@ checks install supporting files, change a native policy, update the complete folder without changing that policy, verify discovery in a fresh process and remove the installation. Claude 2.1.302, Codex 0.163.0 and OpenCode 1.19.3/2.0.32 passed this lifecycle without a model turn. Browser images also -run the Skills UI audit. Cursor/Grok/Muse/Antigravity native activation remains later work. +run the Skills UI audit. Grok, Muse and Pi native user settings can be checked with +`node scripts/agent-resources-native-smoke.mjs PROVIDER /absolute/path/to/executable`. +It uses disposable homes without external inference. Cursor and Antigravity +placement and runtime bridges have separate source and filesystem regressions. Runtime discovery was checked on 2026-10-15 with Claude Code 2.1.300, Codex 0.162.1 and OpenCode 2.0.29. Each CLI ran in a temporary home, with its diff --git a/docs/mcp-servers.md b/docs/mcp-servers.md index 57c20705..d6cd6d34 100644 --- a/docs/mcp-servers.md +++ b/docs/mcp-servers.md @@ -20,11 +20,10 @@ to manage it here. Unsupported entries remain visible and stay unchanged. | Cell | Meaning and actions | | --- | --- | | On | Matches the shared definition and is configured to start in a new session. | -| Off | Codex or OpenCode keeps the entry but does not start it. Its content still matches. | +| Off | The agent keeps the entry but does not start it. Its content still matches. Available in Codex, OpenCode, Grok, Muse, Pi and Antigravity. | | Differs | The native copy has different fields or agent-specific options. Review before replacing it. | | — | Not added. Use Add to copy the definition into this agent’s settings. | | Own entry | Native settings contain this server; it has no shared definition yet. | -| Via Claude Code / Via Cursor | Grok may import that copy. It does not report what it actually loads. | These states describe configuration, not connectivity. Unreadable settings get an attention notice and disable that agent’s controls. While a change saves, its @@ -33,21 +32,29 @@ cell shows progress and that agent’s column is disabled; other agents remain u The **Installed agents / All agents** menu beside **Server** controls the matrix columns, as it does in Skills. It stays visible when the table scrolls sideways. All agents also shows uninstalled agents; their saved entries are read-only. -Muse Code and Antigravity show **Not managed here**, and Grok remains imports only. +All eight agents support native global MCP configuration. Changing the visible columns never adds a server or changes native settings. -Claude Code and Cursor have Add/Remove here. Codex and OpenCode also support + + + MCP server inspector with native controls for all eight agents + + +Claude Code and Cursor have Add/Remove here. The other six agents also support On/Off. Claude’s per-project toggles are outside Setup’s inventory. A legacy -shared `enabled: false` value is still readable and makes new Codex/OpenCode -copies start off; it no longer blocks adding a copy to Claude or Cursor. +shared `enabled: false` value is still readable and makes new copies with On/Off +start off; it no longer blocks adding a copy to Claude or Cursor. ## Add, edit and share -**Add server…** accepts Claude/Cursor JSON or JSONC, Codex TOML, OpenCode V1/V2 -JSON, a `claude mcp add` command, a plain command line or an HTTP URL. Nothing +**Add server…** accepts Claude/Cursor, Muse, Pi and Antigravity JSON, Codex/Grok +TOML, OpenCode V1/V2 JSON, a `claude mcp add` command, a plain command line or an HTTP URL. Nothing pasted is run. When several servers are found, choose one and add the others later. Placeholder values get a warning; replace them with your own credentials. Variable references remain literal and follow each agent’s own rules. +Existing Muse `mcp_servers` sections and Antigravity direct server maps retain +their native layout when adding or removing a server. Both layouts can also be +pasted directly, as can a single native server entry. Command lines use quoting without shell expansion. **Edit one per line** provides one field per argument and preserves empty arguments, surrounding whitespace and @@ -110,11 +117,13 @@ claude mcp login SERVER_NAME codex mcp login SERVER_NAME opencode mcp auth SERVER_NAME cursor-agent mcp login SERVER_NAME +muse mcp login SERVER_NAME +pi mcp login SERVER_NAME ``` Native approval, project configuration, custom T3 provider profiles, enterprise -policy and effective runtime discovery remain with the agents. Grok is imports -only; Muse Code and Antigravity are not managed here. SSE-only addresses cannot +policy and effective runtime discovery remain with the agents. Grok and +Antigravity authentication uses their native MCP interfaces. SSE-only addresses cannot be imported as portable HTTP servers; use the server’s Streamable HTTP address. ## Files and safety @@ -139,9 +148,23 @@ limited to 64 KiB, and performs no execution, expansion, network calls or writes | OpenCode 1 | `opencode.json[c]`, `.mcp[name]` | Local and HTTP; native `enabled` | | OpenCode 2 | `opencode.json[c]`, `.mcp.servers[name]` | Local and HTTP; native `disabled`; existing native V2 files stay V2; new files use the V1-compatible format also accepted by V2 | | Cursor | `~/.cursor/mcp.json` | Local and HTTP; native approval/login still applies | -| Antigravity | T3's isolated provider profile | Not integrated; Gemini CLI settings are not an Antigravity integration | -| Grok Build | `~/.grok/config.toml` | Not managed; Grok may also load Claude/Cursor MCP entries through native compatibility imports | -| Muse Code | `$XDG_CONFIG_HOME/muse/settings.json` | Not managed in this release | +| Antigravity | `~/.gemini/config/mcp_config.json` | Local and HTTP; `serverUrl` and `disabled`; normalized into T3's private profile before launch | +| Grok Build | `$GROK_HOME/config.toml` (default `~/.grok/config.toml`) | Local and HTTP in `mcp_servers`; `headers`, `enabled` and `disabled_mcp_servers`; connected gateway profiles refreshed after saves | +| Muse Code | `$XDG_CONFIG_HOME/muse/settings.json` (default `~/.config/muse/settings.json`) | Local and HTTP in `mcpServers`; `enabled`; preserves schema version 1 and an existing legacy `mcp_servers` section | +| Pi | `$PI_CODING_AGENT_DIR/mcp.json` (default `~/.pi/agent/mcp.json`) | Local and HTTP in `mcpServers`; `enabled`; requires Pi 0.99.0 or later | + +Older Pi versions do not load native `mcp.json`. They show an update message and +cannot receive new copies until updated; existing files remain untouched. The +reserved Pi server name `t3-code` belongs to T3's orchestration extension. +Grok server names must start with a letter. + +The installed Antigravity ACP loader reads remote servers from `url` and does +not honor `disabled`. T3's bridge therefore creates a private snapshot that +maps `serverUrl` to `url` and omits disabled servers. It refreshes at session +startup, preserves private credentials and hooks, and removes its own stale +snapshot when the global source is deleted. Private or manually edited profile +resources are preserved and reported as a launch conflict with their path. +The bridge ships with the maintained Stable and Nightly runtime patches. Claude HTTP entries require an explicit transport type; a URL alone is reported as differing. The documented `streamable-http` alias is recognized. @@ -155,8 +178,9 @@ follow existing file/directory symlinks, reject stale revisions/retargeting and publish atomically with owner-only permissions. Other applications can still race between the final check and rename; this is not a cross-process transaction. The page manages default global files, honoring the server environment’s -Claude/Codex/XDG/OpenCode directory overrides. With credential persistence enabled, -Claude’s default `.claude.json` also lives on the state volume. Custom roots need +Claude/Codex/XDG/OpenCode/Grok/Pi directory overrides. With credential persistence enabled, +all default resources, including Claude's `.claude.json` and Antigravity's +global config, live on the state volume. Custom roots need their own persistent mounts. `t3-browser-mcp` remains a separate quick CLI path for the bundled browser @@ -164,15 +188,16 @@ servers. It writes native entries directly and does not create shared definition ## Verification -Run `node --test tests/mcp*.test.mjs` for parsing, per-agent state, adoption, +Run `node --test tests/mcp*.test.mjs tests/full-agent-resources.test.mjs tests/agent-resources-patch.test.mjs` for parsing, per-agent state, adoption, revisions, redaction, format preservation and the authenticated API boundary. `node scripts/mcp-ui-audit.js URL KEY` exercises desktop, phone and embedded flows in both themes with synthetic data. All API mutations are intercepted. `node scripts/mcp-native-smoke.mjs PROVIDER /absolute/path/to/executable` verifies -local/HTTP handshakes, credential delivery, unlink and Codex/OpenCode Off/On using +local/HTTP handshakes, credential delivery, unlink and native Off/On using fresh native reads, disposable homes and fixture servers. It never sends model -prompts or real credentials. This checks native configuration and MCP negotiation, +requests to an external provider or uses real credentials. Muse uses its local +echo provider. This checks native configuration and MCP negotiation, not model-selected tool calls or OAuth. Native contracts: [Claude](https://code.claude.com/docs/en/mcp), @@ -180,6 +205,7 @@ Native contracts: [Claude](https://code.claude.com/docs/en/mcp), [OpenCode V1](https://opencode.ai/docs/mcp-servers/), [OpenCode V2](https://dev.opencode.ai/v2/docs/mcp-servers/), [Cursor](https://cursor.com/docs/cli/mcp), -[Antigravity](https://antigravity.google/docs/cli/gcli-migration/), +[Antigravity](https://antigravity.google/docs/mcp/), [Grok](https://github.com/xai-org/grok-build/blob/main/crates/codegen/xai-grok-pager/docs/user-guide/07-mcp-servers.md), -and [Muse](https://meta-models.github.io/muse-code-sdk/next/guides/extend/mcp-servers/). +[Muse](https://meta-models.github.io/muse-code-sdk/next/guides/extend/mcp-servers/), +and [Pi](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/mcp.md). diff --git a/docs/media/agent-resources/instructions-dark.png b/docs/media/agent-resources/instructions-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..ca4fdb605383f49f3aae7bd55bb453e11edca2c4 GIT binary patch literal 166349 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

8VvtV%&IunFth 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

ydmP$;=KahQE(;WK9P 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