Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 24 additions & 10 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ Completed environments never reinstall initial files on reconnect or native reco
Provider RunCommand carries bounded stdin, not confidential argv. Only fixed trusted
initializers may run with Runtime authority. User setup and package install hooks
run in the common packaged sandbox, without daemon credentials or native history.
Files and resolved Skills precede system, npm/Python packages and ordered setup commands. Initialization has
Files, resolved Skills and inline Plugins precede system, npm/Python packages and ordered setup commands. Initialization has
provisioning network access; requested network restrictions apply to native tools
after setup. Confidential env and setup snapshots are encrypted independently of
ordinary metadata. Adapters apply tool env only after isolation, never to the
Expand Down Expand Up @@ -297,15 +297,29 @@ Inline and referenced Skill ZIPs use the same confidential initialization snapsh
Core validates portable manifests and bounded regular-file archives, returns only
safe Skill metadata, and freezes content before native preparation. The Runtime
owns `/environment/initialization/capabilities/skills/<name>`; setup and native tools may read but
not modify this tree. The common execution descriptor carries Skill metadata,
never native plugin configuration or template identities. Adapters register native
Skill roots without changing the execution loop or enabling unrestricted tools.
Native activation extensions remain adapter-owned and must fail explicitly when
unqualified. Generic Plugins and capability-directory
imports remain separate work; an adapter-owned Claude plugin envelope does not
implement public Plugins.

Name, enabled/disabled/exact-domain restricted network, initial files, inline/referenced Skills and env/setup/system/npm/Python are
not modify this tree. Skill-only public Plugin ZIPs preserve their complete package
layout and reuse the shared archive and portable Skill parsers. Core keeps safe
Plugin metadata separate from encrypted archives. Templates inherit or replace
Plugin and capability-directory lists through the same hosted resolver.

After ordered setup, the existing initializer snapshots declared workspace-contained
capability directories into protected storage and writes one installed manifest.
This is an initialization artifact, not a second lifecycle owner or database ledger.
Directory bytes are observed after setup; they are not frozen at Session creation.
The common daemon resolves the manifest only for executable preparation and passes
validated Runtime-owned Skill/package roots to adapters. Files reads do not require
that artifact. Reconnection and recovery read installed bytes, never mutable source
directories. Missing or inconsistent installations fail preparation without replay.

Adapters register only selected Skill roots without changing the execution loop.
Codex uses explicit extra roots; MiniMax projects its native catalog; Claude creates
one controlled envelope per package with real directories and immutable hardlinks
under content. Its explicit paths remain inside that envelope; original native
control files are not activated. Keep native MCP discovery disabled. Unsupported
native activation fails explicitly. Public Plugin MCP remains separate qualification,
not silent partial activation or a generic plugin framework.

Name, enabled/disabled/exact-domain restricted network, initial files, inline/referenced Skills, skill-only Plugins, workspace capability directories and env/setup/system/npm/Python are
implemented independently of remaining installation fields. Reject unsupported
inputs rather than persisting them for silent
omission; expand inline and template initialization together in separately qualified
Expand Down
27 changes: 12 additions & 15 deletions apps/parsar-daemon/internal/agent/claudesdk/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import (

"github.com/MiniMax-AI-Dev/parsar/apps/parsar-daemon/internal/localworkspace"
"github.com/MiniMax-AI-Dev/parsar/apps/parsar-daemon/internal/paths"
"github.com/MiniMax-AI-Dev/parsar/internal/agentcapabilities"
"github.com/MiniMax-AI-Dev/parsar/internal/agentdaemon/proto"
"github.com/MiniMax-AI-Dev/parsar/internal/agentnetwork"
"github.com/MiniMax-AI-Dev/parsar/internal/agentskill"
)

// WorkspaceConfig binds one trusted private placement. It does not create an
Expand All @@ -30,17 +30,17 @@ type WorkspaceConfig struct {
}

type workspaceProfile struct {
Skills []agentskill.Metadata `json:"skills,omitempty"`
ToolEnvironment bool `json:"tool_environment,omitempty"`
SystemPackages bool `json:"system_packages,omitempty"`
Home string `json:"home"`
State string `json:"state"`
Scratch string `json:"scratch"`
ProtectedDirs []string `json:"protected_dirs"`
DependencyPath string `json:"dependency_path"`
EnvNames []string `json:"env_names"`
NetworkAccess string `json:"network_access,omitempty"`
AllowedDomains []string `json:"allowed_domains,omitempty"`
Skills []agentcapabilities.InstalledSkill `json:"skills,omitempty"`
ToolEnvironment bool `json:"tool_environment,omitempty"`
SystemPackages bool `json:"system_packages,omitempty"`
Home string `json:"home"`
State string `json:"state"`
Scratch string `json:"scratch"`
ProtectedDirs []string `json:"protected_dirs"`
DependencyPath string `json:"dependency_path"`
EnvNames []string `json:"env_names"`
NetworkAccess string `json:"network_access,omitempty"`
AllowedDomains []string `json:"allowed_domains,omitempty"`
}

func prepareWorkspace(config Config, req proto.PromptRequestPayload) (*workspaceProfile, []string, error) {
Expand All @@ -65,9 +65,6 @@ func prepareWorkspace(config Config, req proto.PromptRequestPayload) (*workspace
profile.SystemPackages = req.LocalEnvironment.SystemPackages
}
if req.LocalEnvironment != nil {
if err := localworkspace.VerifySkills(req.LocalEnvironment.Skills); err != nil {
return nil, nil, err
}
profile.Skills = req.LocalEnvironment.Skills
}
return profile, env, nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func TestRemoteEnvironmentKeepsPathsAndCredentialsSeparate(t *testing.T) {
t.Fatal(err)
}
defer plan.Cleanup()
if plan.Cwd != r.WorkDir || skillRoot != "" || len(plan.Environments) != 1 || plan.Environments[0].Cwd != r.RemoteEnvironment.WorkspaceDirectory || plan.Environments[0].EnvironmentID != "remote" {
if plan.Cwd != r.WorkDir || len(skillRoot) != 0 || len(plan.Environments) != 1 || plan.Environments[0].Cwd != r.RemoteEnvironment.WorkspaceDirectory || plan.Environments[0].EnvironmentID != "remote" {
t.Fatal("remote execution changed harness cwd or installed local skills")
}
if _, err := os.Stat(r.RemoteEnvironment.WorkspaceDirectory); !os.IsNotExist(err) {
Expand Down
9 changes: 3 additions & 6 deletions apps/parsar-daemon/internal/agent/codex/hosted_skills.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,12 @@ import (
"path/filepath"

"github.com/MiniMax-AI-Dev/parsar/apps/parsar-daemon/internal/localworkspace"
"github.com/MiniMax-AI-Dev/parsar/internal/agentskill"
"github.com/MiniMax-AI-Dev/parsar/internal/agentcapabilities"
)

func verifyHostedSkills(skills []agentskill.Metadata) error {
if err := localworkspace.VerifySkills(skills); err != nil {
return err
}
func verifyHostedSkills(skills []agentcapabilities.InstalledSkill) error {
for _, skill := range skills {
if err := verifyHostedSkillLayout(filepath.Join(localworkspace.SkillDirectory, skill.Name)); err != nil {
if err := verifyHostedSkillLayout(localworkspace.SkillPath(skill)); err != nil {
return err
}
}
Expand Down
6 changes: 3 additions & 3 deletions apps/parsar-daemon/internal/agent/codex/preparation.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func newPreparation(parent context.Context, req proto.PromptRequestPayload, cfg
if !req.WorkspaceReadOnly {
req.AgentOptions = executionOptions(req)
}
plan, skillRoot, err := prepareSessionPlan(parent, req, cfg)
plan, skillRoots, err := prepareSessionPlan(parent, req, cfg)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -169,8 +169,8 @@ func newPreparation(parent context.Context, req proto.PromptRequestPayload, cfg
return nil, err
}
}
if skillRoot != "" {
if err := setSkillExtraRoots(cancelCtx, rpc, []string{skillRoot}); err != nil {
if len(skillRoots) > 0 {
if err := setSkillExtraRoots(cancelCtx, rpc, skillRoots); err != nil {
cancelFn()
_ = rpc.Close()
plan.Cleanup()
Expand Down
36 changes: 21 additions & 15 deletions apps/parsar-daemon/internal/agent/codex/session_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,22 @@ import (
"github.com/MiniMax-AI-Dev/parsar/internal/agentdaemon/proto"
)

func prepareSessionPlan(ctx context.Context, req proto.PromptRequestPayload, cfg sessionConfig) (SessionPlan, string, error) {
func prepareSessionPlan(ctx context.Context, req proto.PromptRequestPayload, cfg sessionConfig) (SessionPlan, []string, error) {
profile, err := managedPermissionProfile(req, cfg)
if err != nil {
return SessionPlan{}, "", err
return SessionPlan{}, nil, err
}
if req.WorkspaceReadOnly {
plan, err := workspaceReadPlan(req)
return plan, "", err
return plan, nil, err
}
mcpServers, err := publicMCPHTTPServers(req)
if err != nil {
return SessionPlan{}, "", err
return SessionPlan{}, nil, err
}
plan, err := BuildSessionPlan(req.RunID, req.AgentStateKey, req.WorkDir, req.AgentOptions)
if err != nil {
return SessionPlan{}, "", fmt.Errorf("codex: build session plan: %w", err)
return SessionPlan{}, nil, fmt.Errorf("codex: build session plan: %w", err)
}
if profile != "" {
plan.Sandbox = ""
Expand All @@ -37,13 +37,13 @@ func prepareSessionPlan(ctx context.Context, req proto.PromptRequestPayload, cfg
if req.LocalEnvironment != nil && req.LocalEnvironment.ToolEnvironment {
if err := localworkspace.VerifyToolEnvironment(req.LocalEnvironment.SystemPackages); err != nil {
plan.Cleanup()
return SessionPlan{}, "", err
return SessionPlan{}, nil, err
}
plan.Env = append(plan.Env, "PARSAR_RUNTIME_TOOL_ENV=1")
if req.LocalEnvironment.SystemPackages {
if err := prepareSystemToolAnchor(); err != nil {
plan.Cleanup()
return SessionPlan{}, "", err
return SessionPlan{}, nil, err
}
plan.Env = append(plan.Env, "PARSAR_RUNTIME_SYSTEM_PACKAGES=1")
}
Expand All @@ -57,32 +57,38 @@ func prepareSessionPlan(ctx context.Context, req proto.PromptRequestPayload, cfg
if mcpServers != nil {
if err := configureMCPHTTP(&plan, mcpServers); err != nil {
plan.Cleanup()
return SessionPlan{}, "", err
return SessionPlan{}, nil, err
}
}

if stringOpt(req.AgentOptions, "model_verbosity") != "" {
if err := prepareModelVerbosity(ctx, cfg.codexBinary, &plan); err != nil {
plan.Cleanup()
return SessionPlan{}, "", err
return SessionPlan{}, nil, err
}
}

if req.DisableExecutionEnvironment {
plan.Env = append(plan.Env, "CODEX_EXEC_SERVER_URL=none")
}
skillRoot := ""
var skillRoots []string
if req.LocalEnvironment != nil && len(req.LocalEnvironment.Skills) > 0 {
err = verifyHostedSkills(req.LocalEnvironment.Skills)
if err == nil {
skillRoot = localworkspace.SkillDirectory
for _, skill := range req.LocalEnvironment.Skills {
skillRoots = append(skillRoots, localworkspace.SkillPath(skill))
}
}
} else if !req.DisableExecutionEnvironment && req.RemoteEnvironment == nil {
skillRoot, err = prepareManagedSkills(ctx, cfg.logger, req)
var root string
root, err = prepareManagedSkills(ctx, cfg.logger, req)
if root != "" {
skillRoots = []string{root}
}
}
if err != nil {
plan.Cleanup()
return SessionPlan{}, "", err
return SessionPlan{}, nil, err
}

if req.RemoteEnvironment != nil {
Expand All @@ -92,8 +98,8 @@ func prepareSessionPlan(ctx context.Context, req proto.PromptRequestPayload, cfg
if cfg.runtimeNetwork.Access == "restricted" {
if err := prepareManagedNetwork(&plan, cfg.runtimeNetwork); err != nil {
plan.Cleanup()
return SessionPlan{}, "", err
return SessionPlan{}, nil, err
}
}
return plan, skillRoot, nil
return plan, skillRoots, nil
}
27 changes: 16 additions & 11 deletions apps/parsar-daemon/internal/agent/mcode/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,21 +62,26 @@ func prepareWorkspaceOptions(ctx context.Context, c WorkspaceConfig, req proto.P
if err != nil {
return opts, err
}
if err := localworkspace.VerifySkills(req.LocalEnvironment.Skills); err != nil {
return opts, err
}
if len(req.LocalEnvironment.Skills) > 0 {
link := filepath.Join(opts.DataDir, "skills")
if target, err := os.Readlink(link); err == nil {
if target != localworkspace.SkillDirectory {
return opts, fmt.Errorf("mcode: unexpected native Skill root")
}
} else if !os.IsNotExist(err) {
return opts, err
} else if err := os.Symlink(localworkspace.SkillDirectory, link); err != nil {
root := filepath.Join(opts.DataDir, "skills")
if err := os.MkdirAll(root, 0700); err != nil {
return opts, err
}
for _, skill := range req.LocalEnvironment.Skills {
link, target := filepath.Join(root, skill.Metadata.Name), localworkspace.SkillPath(skill)
actual, err := os.Readlink(link)
if err == nil {
if actual != target {
return opts, fmt.Errorf("mcode: unexpected native Skill root")
}
} else if !os.IsNotExist(err) {
return opts, err
} else if err := os.Symlink(target, link); err != nil {
return opts, err
}
}
}

raw, err := os.ReadFile(filepath.Join(opts.DataDir, "config.yaml"))
if err != nil {
return opts, err
Expand Down
1 change: 1 addition & 0 deletions apps/parsar-daemon/internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func defaultRunContext() *runContext {
// commands lists subcommands in --help render order: the user's
// likely flow connect → status → stop / logs → logout.
var commands = []command{
{name: "runtime-capabilities", summary: "Install frozen capabilities in the packaged Runtime", run: runRuntimeCapabilities},
{name: "placement", summary: "Enroll or retire an explicitly managed local execution placement", run: runPlacement},
{name: "connect", summary: "Pair, open the reverse WebSocket, and start serving prompts", run: runConnect},
{name: "status", summary: "Print the paired profile and daemon state", run: runStatus},
Expand Down
15 changes: 8 additions & 7 deletions apps/parsar-daemon/internal/cli/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,14 @@ func TestSubcommandsAreRegistered(t *testing.T) {
// Guards against dropping a subcommand off the commands slice —
// the public CLI surface is the shipped contract.
want := map[string]bool{
"placement": false,
"connect": false,
"status": false,
"stop": false,
"logs": false,
"logout": false,
"version": false,
"runtime-capabilities": false,
"placement": false,
"connect": false,
"status": false,
"stop": false,
"logs": false,
"logout": false,
"version": false,
}
for _, c := range commands {
if _, ok := want[c.name]; !ok {
Expand Down
56 changes: 56 additions & 0 deletions apps/parsar-daemon/internal/cli/runtime_capabilities.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package cli

import (
"encoding/json"
"fmt"
"io"
"os"

"github.com/MiniMax-AI-Dev/parsar/internal/agentcapabilities"
)

// This packaged helper is invoked by the existing Core initializer. It has no
// daemon connection, credentials, scheduling or retry behavior.
func runRuntimeCapabilities(ctx *runContext, args []string) error {
if len(args) != 0 {
return agentcapabilities.ErrInvalid
}
var request agentcapabilities.Operation
decoder := json.NewDecoder(io.LimitReader(os.Stdin, 32<<20))
decoder.DisallowUnknownFields()
if decoder.Decode(&request) != nil || request.Version != 1 {
return agentcapabilities.ErrInvalid
}
var extra any
if decoder.Decode(&extra) != io.EOF {
return agentcapabilities.ErrInvalid
}
root, err := os.OpenRoot("/environment")
if err != nil {
return agentcapabilities.ErrInvalid
}
defer root.Close()
installed, err := root.OpenRoot("initialization/capabilities")
if err != nil {
return agentcapabilities.ErrInvalid
}
defer installed.Close()
switch request.Action {
case "plugin":
err = agentcapabilities.InstallPlugin(installed, request.Slot, request.Archive, request.Plugin)
case "finalize":
workspace, openErr := root.OpenRoot("workspace")
if openErr != nil {
return agentcapabilities.ErrInvalid
}
defer workspace.Close()
err = agentcapabilities.Finalize(workspace, installed, request.Sources)
default:
err = agentcapabilities.ErrInvalid
}
if err != nil {
return agentcapabilities.ErrInvalid
}
_, err = fmt.Fprintln(ctx.stdout, `{"version":1,"outcome":"completed"}`)
return err
}
10 changes: 10 additions & 0 deletions apps/parsar-daemon/internal/localworkspace/binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@ func (b *Binding) Configure(r proto.PromptRequestPayload) (proto.PromptRequestPa
return r, err
}
}
local := *r.LocalEnvironment
local.Skills = nil
if local.Capabilities {
var err error
local.Skills, err = LoadSkills()
if err != nil {
return r, err
}
}
r.LocalEnvironment = &local
r.WorkDir = b.workspace
}
return r, nil
Expand Down
Loading
Loading