From e72543df09369f68ada77254bfbfc31e1cc8cc6d Mon Sep 17 00:00:00 2001 From: saladday <1203511142@qq.com> Date: Sun, 20 Sep 2026 00:49:26 +0800 Subject: [PATCH] feat(agents-api): qualify MiniMax Code Docker workspace runtime --- .github/workflows/check.yml | 6 +- CONTRIBUTING.md | 25 +- Makefile | 8 +- .../internal/agent/mcode/events.go | 7 +- .../internal/agent/mcode/preparation.go | 131 ++ .../internal/agent/mcode/preparation_test.go | 123 ++ .../internal/agent/mcode/session.go | 41 +- .../internal/agent/mcode/session_test.go | 12 +- .../internal/agent/mcode/tool_observations.go | 55 + .../agent/mcode/tool_observations_test.go | 49 + .../internal/agent/mcode/workspace.go | 90 + .../agent/mcode/workspace_readiness.go | 38 + .../internal/cli/agent_discovery.go | 14 +- .../internal/cli/agent_registration.go | 7 +- .../internal/cli/mcode_workspace.go | 76 + contracts/agents-api/harness-onboarding.md | 6 + contracts/agents-api/mcode-workspace-v1.md | 158 ++ packages/mcode-harness/README.md | 54 + packages/mcode-harness/bridge.mjs | 34 + packages/mcode-harness/build-sandbox.mjs | 18 + packages/mcode-harness/build.mjs | 31 + packages/mcode-harness/check.mjs | 14 + packages/mcode-harness/launch.mjs | 33 + packages/mcode-harness/native-pi-tools.ts | 5 + packages/mcode-harness/native.test.mjs | 36 + packages/mcode-harness/package-lock.json | 1841 +++++++++++++++++ packages/mcode-harness/package.json | 28 + packages/mcode-harness/sandbox-entry.ts | 1 + packages/mcode-harness/source.json | 5 + packages/mcode-harness/tool-executor.mjs | 65 + packages/mcode-harness/tool-executor.test.mjs | 64 + packages/mcode-harness/worker.ts | 32 + pnpm-workspace.yaml | 2 + scripts/build-mcode-harness.sh | 49 + scripts/build-mcode-runtime.sh | 33 + services/agents-api/deploy/mcode/Dockerfile | 26 + services/agents-api/deploy/mcode/README.md | 63 +- services/agents-api/internal/engine/mcode.go | 6 +- .../internal/execution/mcode_profile_test.go | 2 +- 39 files changed, 3249 insertions(+), 39 deletions(-) create mode 100644 apps/parsar-daemon/internal/agent/mcode/preparation.go create mode 100644 apps/parsar-daemon/internal/agent/mcode/preparation_test.go create mode 100644 apps/parsar-daemon/internal/agent/mcode/tool_observations.go create mode 100644 apps/parsar-daemon/internal/agent/mcode/tool_observations_test.go create mode 100644 apps/parsar-daemon/internal/agent/mcode/workspace.go create mode 100644 apps/parsar-daemon/internal/agent/mcode/workspace_readiness.go create mode 100644 apps/parsar-daemon/internal/cli/mcode_workspace.go create mode 100644 contracts/agents-api/mcode-workspace-v1.md create mode 100644 packages/mcode-harness/README.md create mode 100644 packages/mcode-harness/bridge.mjs create mode 100644 packages/mcode-harness/build-sandbox.mjs create mode 100644 packages/mcode-harness/build.mjs create mode 100644 packages/mcode-harness/check.mjs create mode 100644 packages/mcode-harness/launch.mjs create mode 100644 packages/mcode-harness/native-pi-tools.ts create mode 100644 packages/mcode-harness/native.test.mjs create mode 100644 packages/mcode-harness/package-lock.json create mode 100644 packages/mcode-harness/package.json create mode 100644 packages/mcode-harness/sandbox-entry.ts create mode 100644 packages/mcode-harness/source.json create mode 100644 packages/mcode-harness/tool-executor.mjs create mode 100644 packages/mcode-harness/tool-executor.test.mjs create mode 100644 packages/mcode-harness/worker.ts create mode 100644 scripts/build-mcode-harness.sh create mode 100644 scripts/build-mcode-runtime.sh create mode 100644 services/agents-api/deploy/mcode/Dockerfile diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 984983b3..adc2707d 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -17,6 +17,7 @@ on: - "packages/cli/**" - "packages/opencode-plugin/**" - "packages/claude-sdk-adapter/**" + - "packages/mcode-harness/**" - "packages/eslint-config/**" - "packages/tsconfig/**" - "scripts/**" @@ -42,6 +43,7 @@ on: - "packages/cli/**" - "packages/opencode-plugin/**" - "packages/claude-sdk-adapter/**" + - "packages/mcode-harness/**" - "packages/eslint-config/**" - "packages/tsconfig/**" - "scripts/**" @@ -116,7 +118,7 @@ jobs: apps/web/*) web=true ;; - packages/cli/*|packages/opencode-plugin/*|packages/claude-sdk-adapter/*) + packages/cli/*|packages/opencode-plugin/*|packages/claude-sdk-adapter/*|packages/mcode-harness/*) cli=true ;; packages/eslint-config/*) @@ -237,7 +239,7 @@ jobs: - run: pnpm install --frozen-lockfile - - run: make check-cli + - run: make check-cli check-mcode-harness check-gate: needs: [changes, check-go, check-store, check-web, check-cli] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 20f1aa5c..63d3a8bb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -198,13 +198,17 @@ the same transaction as its Session and creation identity when this resource is implemented. Keep mutable connection/registration state out of immutable configuration; replacement ownership must fence stale observations. -The accepted delivery baseline is the shared Codex/Claude single-Agent path: Session +The current MVP covers Codex, Claude Code and MiniMax Code through the shared +single-Agent path: Session creation, environment preparation, native execution, files/artifacts, cancellation, reconnection/recovery queries, and standalone deployment acceptance. Select each bounded task from the complete board; nonblocking local improvements stay queued. Authentication, tenant/credential isolation, state consistency and data loss remain -material acceptance requirements. Additional harness implementations and protocol Subagent execution -remain queued without changing the complete pinned protocol target. +material acceptance requirements. Optional feature equality is not required. After +the three profiles pass merged-main validation, publish the results, limitations +and backlog, then stop development until new user direction. Additional harness +implementations and protocol Subagent execution remain queued without changing the +complete pinned protocol target. The current hosted architecture is V1: Core runs independently; each Environment sandbox contains its daemon, selected native harness, local tools and workspace. @@ -1983,6 +1987,21 @@ settles the process and output, and continuation requires the exact owned native history. Do not infer history IDs or qualify hosted execution from this text profile. See [deployment and acceptance](services/agents-api/deploy/mcode/README.md). +The MiniMax workspace profile retains the published CLI and isolates native +workspace tools behind its standard MCP client. The process and native Session +share one private control directory; public workspace files cannot configure that +process or become privileged project instructions. A trusted adapter-owned bridge +runs the original six tool implementations in the upstream Linux sandbox, with no +unsandboxed fallback. Keep native history bound to the control directory and Files/ +Artifacts bound to the public workspace. Core and shared file helpers remain engine +neutral. This internal MCP transport does not admit public MCP configuration. +Record published CLI and worker-source provenance separately; complete +[workspace acceptance](contracts/agents-api/mcode-workspace-v1.md) before enabling +hosted execution. The standalone companion uses its own npm lock; `make check` +runs its lifecycle tests and script checks, while its exact-source Linux build and +Docker qualification (including `native.test.mjs` under both network policies) +are required when the companion changes. + Claude hosted functions compose the existing SDK function bridge with the native workspace sandbox. Only declared function tools and the verified native tool inventory are available. The bundle advertises this combination separately from diff --git a/Makefile b/Makefile index 91c1c4f6..8966861f 100644 --- a/Makefile +++ b/Makefile @@ -89,7 +89,7 @@ dev: dev-db # The full gate runs independent API tests once, after their isolated build. check: GO_TEST_EXCLUDE = $(if $(strip $(GO_TEST_RUN) $(GO_TEST_ARGS)),,github.com/MiniMax-AI-Dev/parsar/services/agents-api/% github.com/MiniMax-AI-Dev/parsar/packages/agents-client/%) -check: check-go check-store check-web check-cli check-hygiene check-installer check-agents-api check-agents-executor check-agents-harness +check: check-go check-store check-web check-cli check-hygiene check-installer check-agents-api check-agents-executor check-agents-harness check-mcode-harness @printf 'Parsar harness checks passed.\n' check-setup: @@ -132,6 +132,12 @@ check-cli: check-setup node-deps check-installer: bash scripts/check-installer.sh +.PHONY: check-mcode-harness +check-mcode-harness: + node --test packages/mcode-harness/*.test.mjs + @for script in packages/mcode-harness/*.mjs; do node --check "$$script"; done + bash -n scripts/build-mcode-harness.sh scripts/build-mcode-runtime.sh + check-hygiene: check-setup @for polluted in .parsar logs state cache config; do \ if [[ -e "$$polluted" ]]; then \ diff --git a/apps/parsar-daemon/internal/agent/mcode/events.go b/apps/parsar-daemon/internal/agent/mcode/events.go index ac7a63df..5a6b83e2 100644 --- a/apps/parsar-daemon/internal/agent/mcode/events.go +++ b/apps/parsar-daemon/internal/agent/mcode/events.go @@ -66,11 +66,14 @@ func (s *Session) emitTool(update toolUpdate) { if update.RawInput == nil { update.RawInput = previous.RawInput } + if s.req.ObserveToolObservations { + started = workspaceToolObservation(previous, "before") != nil + } if !started { - s.emit(proto.TypeToolCall, proto.ToolCallPayload{ID: update.ID, Name: update.Name, Stage: "before", Args: update.RawInput}) + s.emitToolStage(update, "before") } if update.Status == "completed" || update.Status == "failed" { - s.emit(proto.TypeToolCall, proto.ToolCallPayload{ID: update.ID, Name: update.Name, Stage: "after", Result: map[string]any{"output": update.RawOutput, "status": update.Status}}) + s.emitToolStage(update, "after") delete(s.tools, update.ID) s.completedTools[update.ID] = true } else { diff --git a/apps/parsar-daemon/internal/agent/mcode/preparation.go b/apps/parsar-daemon/internal/agent/mcode/preparation.go new file mode 100644 index 00000000..74058a5d --- /dev/null +++ b/apps/parsar-daemon/internal/agent/mcode/preparation.go @@ -0,0 +1,131 @@ +package mcode + +import ( + "context" + "fmt" + "strings" + "sync" + + "github.com/MiniMax-AI-Dev/parsar/apps/parsar-daemon/internal/agent" + "github.com/MiniMax-AI-Dev/parsar/internal/agentdaemon/proto" +) + +type prepared struct { + mu sync.Mutex + session *Session + ready chan struct{} + started chan struct{} + failure error + closed bool + binding *preparedStart +} + +type preparedStart struct { + runID, prompt string + out chan<- proto.Envelope +} + +func NewPreparationFactory(config WorkspaceConfig) agent.PreparationFactory { + return func(ctx context.Context, req proto.PromptRequestPayload) (agent.Prepared, error) { + if ctx == nil { + ctx = context.Background() + } + if req.RunID != "" || req.Prompt != "" || req.ConversationID != "" || req.ObserveSubagentIdentities { + return nil, fmt.Errorf("mcode: preparation cannot contain input or product context") + } + opts, err := prepareWorkspaceOptions(ctx, config, req) + if err != nil { + return nil, err + } + s, err := launch(ctx, req, opts, config.Binary, nil) + if err != nil { + return nil, err + } + p := &prepared{session: s, ready: make(chan struct{}), started: make(chan struct{})} + go s.run(p) + <-p.ready + if p.failure != nil { + <-s.finished + return nil, p.failure + } + if ctx.Err() != nil { + _ = p.Close() + return nil, ctx.Err() + } + return p, nil + } +} + +func (p *prepared) Start(ctx context.Context, runID, prompt string, out chan<- proto.Envelope) (agent.Session, error) { + if ctx == nil { + ctx = context.Background() + } + p.mu.Lock() + defer p.mu.Unlock() + if p.closed || p.binding != nil { + return nil, fmt.Errorf("mcode: preparation is no longer available") + } + if strings.TrimSpace(runID) == "" || strings.TrimSpace(prompt) == "" || out == nil || ctx.Err() != nil { + return nil, fmt.Errorf("mcode: start requires a live context, identity, prompt and output") + } + select { + case <-p.session.process.Context().Done(): + return nil, fmt.Errorf("mcode: prepared process ended") + case <-p.session.exited: + return nil, fmt.Errorf("mcode: prepared process exited") + default: + } + p.binding = &preparedStart{runID: runID, prompt: prompt, out: out} + close(p.started) + return p.session, nil +} + +func (p *prepared) Close() error { + p.mu.Lock() + if p.binding != nil { + p.mu.Unlock() + return nil + } + p.closed = true + p.mu.Unlock() + return p.session.Cancel(context.Background()) +} + +func (p *prepared) Cancel(ctx context.Context) error { + p.mu.Lock() + p.closed = true + p.mu.Unlock() + return p.session.Cancel(ctx) +} + +func (p *prepared) CancellationOutcome() proto.DonePayload { return p.session.CancellationOutcome() } + +// The Session goroutine owns preparation, input submission and terminal output. +func (p *prepared) awaitStart(err error) error { + p.failure = err + close(p.ready) + if err != nil { + return err + } + for { + var ended error + select { + case <-p.started: + case <-p.session.process.Context().Done(): + ended = p.session.process.Context().Err() + case _, ok := <-p.session.frames: + if ok { + continue + } + ended = fmt.Errorf("mcode: prepared process exited") + } + p.mu.Lock() + if b := p.binding; b != nil { + p.session.req.RunID, p.session.req.Prompt, p.session.out = b.runID, b.prompt, b.out + } else { + p.closed = true + } + p.mu.Unlock() + return ended + } +} diff --git a/apps/parsar-daemon/internal/agent/mcode/preparation_test.go b/apps/parsar-daemon/internal/agent/mcode/preparation_test.go new file mode 100644 index 00000000..3eb1220f --- /dev/null +++ b/apps/parsar-daemon/internal/agent/mcode/preparation_test.go @@ -0,0 +1,123 @@ +package mcode + +import ( + "context" + "encoding/json" + "os" + "path/filepath" + "strings" + "sync" + "testing" + "time" + + "github.com/MiniMax-AI-Dev/parsar/internal/agentdaemon/proto" +) + +func workspaceFixture(t *testing.T) (WorkspaceConfig, proto.PromptRequestPayload, string) { + t.Helper() + r := executionRequest(t) + r.RunID, r.Prompt, r.ConversationID = "", "", "" + r.DisableExecutionEnvironment = false + r.LocalEnvironment = &proto.LocalEnvironment{ID: "environment", NetworkAccess: "disabled"} + r.WorkDir = t.TempDir() + record := filepath.Join(t.TempDir(), "calls") + exe, err := os.Executable() + if err != nil { + t.Fatal(err) + } + binary := filepath.Join(t.TempDir(), "native") + quote := func(s string) string { return "'" + strings.ReplaceAll(s, "'", "'\\''") + "'" } + script := "#!/bin/sh\nexport PARSAR_MCODE_TEST_HELPER=prepared\nexport PARSAR_MCODE_TEST_RECORD=" + quote(record) + "\nexec " + quote(exe) + " -test.run=^TestMCodeProcess$ -- \"$@\"\n" + if err := os.WriteFile(binary, []byte(script), 0700); err != nil { + t.Fatal(err) + } + return WorkspaceConfig{Binary: binary, Node: "/usr/bin/node", Bridge: "/opt/bridge.mjs", Directory: r.WorkDir, Network: "disabled", Scratch: t.TempDir(), ProtectedDirs: []string{os.Getenv("PARSAR_HOME")}}, r, record +} + +func TestPreparedWorkspaceHasOneInputAndOutputOwner(t *testing.T) { + c, r, record := workspaceFixture(t) + ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) + defer cancel() + resource, err := NewPreparationFactory(c)(ctx, r) + if err != nil { + t.Fatal(err) + } + p := resource.(*prepared) + t.Cleanup(func() { _ = p.Cancel(context.Background()) }) + raw, err := os.ReadFile(record) + if err != nil || strings.Contains(string(raw), "session/prompt") { + t.Fatalf("preparation consumed input: %q %v", raw, err) + } + if p.session.opts.Dir == r.WorkDir || !strings.HasPrefix(p.session.opts.Dir, p.session.opts.DataDir+string(filepath.Separator)) { + t.Fatal("native cwd is not private") + } + out := make(chan proto.Envelope) + var wg sync.WaitGroup + winners := make(chan bool, 8) + for range 8 { + wg.Add(1) + go func() { defer wg.Done(); _, err := p.Start(ctx, "run", "input", out); winners <- err == nil }() + } + wg.Wait() + close(winners) + n := 0 + for winner := range winners { + if winner { + n++ + } + } + if n != 1 { + t.Fatalf("owners=%d", n) + } + if err := p.Close(); err != nil { + t.Fatal(err) + } + deltas, done := 0, 0 + for e := range out { + if e.Type == proto.TypeError { + t.Fatalf("execution: %s", e.Payload) + } + if e.Type == proto.TypeDelta { + deltas++ + } + if e.Type == proto.TypeDone { + done++ + select { + case <-p.session.exited: + default: + t.Fatal("Done before native settlement") + } + var d proto.DonePayload + _ = json.Unmarshal(e.Payload, &d) + if d.Metadata[proto.DoneMetaAgentSessionID] != "native-1" { + t.Fatal("native identity lost") + } + } + } + if deltas != 100 || done != 1 { + t.Fatalf("deltas=%d done=%d", deltas, done) + } + raw, _ = os.ReadFile(record) + if strings.Count(string(raw), "session/prompt") != 1 { + t.Fatalf("inputs=%s", raw) + } +} + +func TestPreparedWorkspaceCloseBeforeStart(t *testing.T) { + c, r, _ := workspaceFixture(t) + ctx, cancel := context.WithTimeout(t.Context(), 5*time.Second) + defer cancel() + resource, err := NewPreparationFactory(c)(ctx, r) + if err != nil { + t.Fatal(err) + } + if err = resource.Close(); err != nil { + t.Fatal(err) + } + if _, err = resource.Start(ctx, "run", "input", make(chan proto.Envelope)); err == nil { + t.Fatal("released preparation started") + } + if err = resource.Close(); err != nil { + t.Fatal(err) + } +} diff --git a/apps/parsar-daemon/internal/agent/mcode/session.go b/apps/parsar-daemon/internal/agent/mcode/session.go index 59ffdf9a..ffe14878 100644 --- a/apps/parsar-daemon/internal/agent/mcode/session.go +++ b/apps/parsar-daemon/internal/agent/mcode/session.go @@ -62,6 +62,15 @@ func newSession(ctx context.Context, req proto.PromptRequestPayload, out chan<- if err != nil { return nil, err } + s, err := launch(ctx, req, opts, binary, out) + if err != nil { + return nil, err + } + go s.run(nil) + return s, nil +} + +func launch(ctx context.Context, req proto.PromptRequestPayload, opts launchOptions, binary string, out chan<- proto.Envelope) (*Session, error) { process, err := clirunner.Start(clirunner.StartOptions{Parent: ctx, Binary: binary, Args: []string{"acp"}, Dir: opts.Dir, Env: opts.Env, NeedStdin: true, OwnProcessGroup: req.StrictResume}) if err != nil { return nil, err @@ -69,7 +78,6 @@ func newSession(ctx context.Context, req proto.PromptRequestPayload, out chan<- s := &Session{ctx: ctx, req: req, opts: opts, process: process, out: out, frames: make(chan rpcFrame, 32), exited: make(chan struct{}), finished: make(chan struct{}), responses: map[string]chan rpcFrame{}, permissions: map[string]pendingPermission{}, questions: map[string]pendingQuestion{}, tools: map[string]toolUpdate{}, completedTools: map[string]bool{}} go func() { _, _ = io.Copy(io.Discard, process.Stderr) }() go s.read() - go s.run() return s, nil } @@ -116,10 +124,27 @@ func (s *Session) read() { } } -func (s *Session) run() { - defer close(s.out) +func (s *Session) run(p *prepared) { + defer func() { + if s.out != nil { + close(s.out) + } + }() defer close(s.finished) - err := s.execute() + err := s.prepareNative() + if p != nil { + err = p.awaitStart(err) + } + if err == nil { + err = s.executePrompt() + } + if p != nil || err != nil { + s.process.Cancel() + <-s.exited + } + if s.out == nil { + return + } s.mu.Lock() s.steeringReady = false s.mu.Unlock() @@ -140,7 +165,7 @@ func (s *Session) run() { s.emit(proto.TypeDone, proto.DonePayload{Content: s.content.String(), Metadata: metadata}) } -func (s *Session) execute() error { +func (s *Session) prepareNative() error { var initialized struct { ProtocolVersion int `json:"protocolVersion"` } @@ -181,11 +206,15 @@ func (s *Session) execute() error { if err := s.call("session/set_config_option", map[string]any{"sessionId": session.SessionID, "configId": "model", "value": model}, nil, false); err != nil { return err } + return nil +} + +func (s *Session) executePrompt() error { s.active = true var result struct { StopReason string `json:"stopReason"` } - err = s.call("session/prompt", map[string]any{"sessionId": session.SessionID, "prompt": promptContent(s.req.Prompt, s.req.StrictResume)}, &result, true) + err := s.call("session/prompt", map[string]any{"sessionId": s.sessionID, "prompt": promptContent(s.req.Prompt, s.req.StrictResume)}, &result, true) s.active = false s.mu.Lock() s.steeringReady = false diff --git a/apps/parsar-daemon/internal/agent/mcode/session_test.go b/apps/parsar-daemon/internal/agent/mcode/session_test.go index 3e0092b5..06a0d46f 100644 --- a/apps/parsar-daemon/internal/agent/mcode/session_test.go +++ b/apps/parsar-daemon/internal/agent/mcode/session_test.go @@ -213,6 +213,14 @@ func TestMCodeProcess(t *testing.T) { if scenario == "hang" { continue } + if record := os.Getenv("PARSAR_MCODE_TEST_RECORD"); record != "" { + f, err := os.OpenFile(record, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) + if err != nil { + os.Exit(10) + } + _, _ = f.WriteString(frame.Method + "\n") + _ = f.Close() + } result := any(map[string]any{}) switch frame.Method { case "initialize": @@ -240,7 +248,7 @@ func TestMCodeProcess(t *testing.T) { Prompt []map[string]string `json:"prompt"` } _ = json.Unmarshal(frame.Params, &input) - if (scenario == "strict-cancel" && len(input.Prompt) != 2) || (scenario != "strict-cancel" && len(input.Prompt) != 1) { + if strict := scenario == "strict-cancel" || scenario == "prepared"; (strict && len(input.Prompt) != 2) || (!strict && len(input.Prompt) != 1) { os.Exit(9) } if scenario == "steering" || scenario == "steer-rejected" || scenario == "steer-lost" || scenario == "strict-cancel" { @@ -248,7 +256,7 @@ func TestMCodeProcess(t *testing.T) { update("agent_message_chunk", map[string]any{"content": map[string]string{"type": "text", "text": "ready"}}) continue } - if scenario == "many-frames" { + if scenario == "many-frames" || scenario == "prepared" { for range 100 { update("agent_message_chunk", map[string]any{"content": map[string]string{"type": "text", "text": "x"}}) } diff --git a/apps/parsar-daemon/internal/agent/mcode/tool_observations.go b/apps/parsar-daemon/internal/agent/mcode/tool_observations.go new file mode 100644 index 00000000..d48b386b --- /dev/null +++ b/apps/parsar-daemon/internal/agent/mcode/tool_observations.go @@ -0,0 +1,55 @@ +package mcode + +import ( + "encoding/json" + + "github.com/MiniMax-AI-Dev/parsar/internal/agentdaemon/proto" +) + +func (s *Session) emitToolStage(update toolUpdate, stage string) { + payload := proto.ToolCallPayload{ID: update.ID, Name: update.Name, Stage: stage, Args: update.RawInput} + if stage == "after" { + payload.Result = map[string]any{"output": update.RawOutput, "status": update.Status} + } + if s.req.ObserveToolObservations { + payload.Observation = workspaceToolObservation(update, stage) + // Native task/skill bookkeeping has no qualified public item mapping. + if payload.Observation == nil { + return + } + } + s.emit(proto.TypeToolCall, payload) +} + +func workspaceToolObservation(update toolUpdate, stage string) *proto.ToolObservation { + if update.Name != "mcp__parsar_workspace__workspace_bash" { + return nil + } + command, _ := update.RawInput["command"].(string) + if command == "" { + return nil + } + cwd := "/workspace" + result := &proto.ToolObservation{Kind: "command", Command: command, Cwd: &cwd, Status: "in_progress"} + if stage == "after" { + result.Status = update.Status + // ACP reports MCP content but no structured exit code or duration. + raw, _ := json.Marshal(update.RawOutput) + var output struct { + Content []struct { + Type string `json:"type"` + Text string `json:"text"` + } `json:"content"` + } + if json.Unmarshal(raw, &output) == nil && output.Content != nil { + text := "" + for _, part := range output.Content { + if part.Type == "text" { + text += part.Text + } + } + result.Output, _ = json.Marshal(text) + } + } + return result +} diff --git a/apps/parsar-daemon/internal/agent/mcode/tool_observations_test.go b/apps/parsar-daemon/internal/agent/mcode/tool_observations_test.go new file mode 100644 index 00000000..2f6fb496 --- /dev/null +++ b/apps/parsar-daemon/internal/agent/mcode/tool_observations_test.go @@ -0,0 +1,49 @@ +package mcode + +import ( + "context" + "encoding/json" + "testing" + + "github.com/MiniMax-AI-Dev/parsar/internal/agentdaemon/proto" +) + +func TestWorkspaceCommandObservationsWaitForArgumentsAndRetainOutcome(t *testing.T) { + for _, status := range []string{"completed", "failed"} { + t.Run(status, func(t *testing.T) { + out := make(chan proto.Envelope, 8) + s := &Session{ctx: context.Background(), req: proto.PromptRequestPayload{RunID: "run", ObserveToolObservations: true}, out: out, tools: map[string]toolUpdate{}, completedTools: map[string]bool{}} + s.emitTool(toolUpdate{ID: "call", Name: "mcp__parsar_workspace__workspace_bash"}) + if len(out) != 0 { + t.Fatal("command item emitted before native arguments") + } + s.emitTool(toolUpdate{ID: "call", RawInput: map[string]any{"command": "pwd"}}) + s.emitTool(toolUpdate{ID: "call", Status: status, RawOutput: map[string]any{"content": []any{map[string]string{"type": "text", "text": "/workspace"}}}}) + s.emitTool(toolUpdate{ID: "call", Status: status}) + if len(out) != 2 { + t.Fatalf("events=%d", len(out)) + } + var before, after proto.ToolCallPayload + _ = json.Unmarshal((<-out).Payload, &before) + _ = json.Unmarshal((<-out).Payload, &after) + if before.Observation == nil || before.Observation.Kind != "command" || before.Observation.Status != "in_progress" { + t.Fatal(before) + } + n := after.Observation + if n == nil || n.Command != "pwd" || n.Cwd == nil || *n.Cwd != "/workspace" || n.Status != status { + t.Fatal(after) + } + if string(n.Output) != `"/workspace"` || n.ExitCode != nil || n.DurationMS != nil { + t.Fatal(n) + } + }) + } +} + +func TestPrivateUtilitiesAreNotInventedPublicFunctionCalls(t *testing.T) { + for _, name := range []string{"mcp__parsar_workspace__workspace_read", "skill", "task_query", "task_output", "task_stop", "mcp__unregistered__workspace_bash"} { + if workspaceToolObservation(toolUpdate{Name: name}, "before") != nil { + t.Fatal(name) + } + } +} diff --git a/apps/parsar-daemon/internal/agent/mcode/workspace.go b/apps/parsar-daemon/internal/agent/mcode/workspace.go new file mode 100644 index 00000000..5d75fb95 --- /dev/null +++ b/apps/parsar-daemon/internal/agent/mcode/workspace.go @@ -0,0 +1,90 @@ +package mcode + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "slices" + "strings" + + "github.com/MiniMax-AI-Dev/parsar/internal/agentdaemon/proto" +) + +// WorkspaceConfig is frozen deployment input, separate from public Agent options. +type WorkspaceConfig struct { + Binary, Node, Bridge, Directory, Network, Scratch string + ProtectedDirs []string +} + +func ConfigureLocal(binary, node, bridge, root, workspace, network, staging string) (WorkspaceConfig, error) { + c := WorkspaceConfig{Binary: binary, Node: node, Bridge: bridge, Directory: workspace, Network: network, + Scratch: filepath.Join(root, "runtime", "mcode-tools", "scratch"), + ProtectedDirs: []string{filepath.Join(root, "parsar-daemon"), filepath.Join(root, "runtime", "mcode"), filepath.Dir(workspace), staging}} + if network != "enabled" && network != "disabled" { + return c, fmt.Errorf("mcode: explicit workspace network policy is required") + } + for _, path := range []string{binary, node, bridge, root, workspace, staging} { + if !filepath.IsAbs(path) || filepath.Clean(path) != path || path == "/" { + return c, fmt.Errorf("mcode: canonical absolute deployment paths are required") + } + } + for _, path := range []string{binary, node, bridge} { + resolved, err := filepath.EvalSymlinks(path) + if err != nil || resolved != path || strings.HasPrefix(path, workspace+"/") || strings.HasPrefix(path, "/workspace/") { + return c, fmt.Errorf("mcode: runtime programs must be canonical paths outside the workspace") + } + } + bound, err := os.Stat(workspace) + public, publicErr := os.Stat("/workspace") + if err != nil || publicErr != nil || !bound.IsDir() || !os.SameFile(bound, public) { + return c, fmt.Errorf("mcode: public workspace alias does not match the Runtime binding") + } + if err := os.MkdirAll(c.Scratch, 0700); err != nil { + return c, err + } + return c, nil +} + +func prepareWorkspaceOptions(ctx context.Context, c WorkspaceConfig, req proto.PromptRequestPayload) (launchOptions, error) { + if !req.StrictResume || req.LocalEnvironment == nil || req.WorkDir != c.Directory || req.DisableExecutionEnvironment || req.LocalEnvironment.NetworkAccess != c.Network || req.RemoteEnvironment != nil || req.WorkspaceReadOnly { + return launchOptions{}, fmt.Errorf("mcode: execution does not match the dedicated workspace") + } + // Reuse public option validation and private Session state provisioning. Native + // cwd remains private; only the internal MCP worker receives the public workspace. + private := req + private.LocalEnvironment, private.WorkDir, private.DisableExecutionEnvironment = nil, "", true + opts, err := prepareOptions(ctx, private) + if err != nil { + return opts, err + } + raw, err := os.ReadFile(filepath.Join(opts.DataDir, "config.yaml")) + if err != nil { + return opts, err + } + var config map[string]any + if err = json.Unmarshal(raw, &config); err != nil { + return opts, err + } + config["permissionMode"] = "bypassPermissions" + config["sandbox"] = map[string]bool{"enabled": false} + raw, err = json.Marshal(config) + if err != nil { + return opts, err + } + if err = os.WriteFile(filepath.Join(opts.DataDir, "config.yaml"), raw, 0600); err != nil { + return opts, err + } + profile := map[string]any{"workspace": "/workspace", "scratch": c.Scratch, "network": c.Network, "protectedDirs": slices.Clone(c.ProtectedDirs)} + raw, err = json.Marshal(profile) + if err != nil { + return opts, err + } + path := filepath.Join(opts.DataDir, "workspace-profile.json") + if err = os.WriteFile(path, raw, 0600); err != nil { + return opts, err + } + opts.MCP = []map[string]any{{"name": "parsar_workspace", "command": c.Node, "args": []string{c.Bridge, path}, "env": []map[string]string{}}} + return opts, nil +} diff --git a/apps/parsar-daemon/internal/agent/mcode/workspace_readiness.go b/apps/parsar-daemon/internal/agent/mcode/workspace_readiness.go new file mode 100644 index 00000000..84b71f21 --- /dev/null +++ b/apps/parsar-daemon/internal/agent/mcode/workspace_readiness.go @@ -0,0 +1,38 @@ +package mcode + +import ( + "context" + "encoding/json" + "fmt" + "io" + "path/filepath" + "time" + + "github.com/MiniMax-AI-Dev/parsar/apps/parsar-daemon/internal/agent/clirunner" +) + +// CheckWorkspace verifies the installed companion. Public qualification remains +// an operator deployment requirement, not an implication of this probe. +func CheckWorkspace(ctx context.Context, c WorkspaceConfig) error { + ctx, cancel := context.WithTimeout(ctx, 15*time.Second) + defer cancel() + p, err := clirunner.Start(clirunner.StartOptions{Parent: ctx, Binary: c.Node, Args: []string{filepath.Join(filepath.Dir(c.Bridge), "check.mjs")}, Env: executionEnvironment(), OwnProcessGroup: true}) + if err != nil { + return err + } + defer p.Cancel() + go func() { _, _ = io.Copy(io.Discard, p.Stderr) }() + raw, err := io.ReadAll(io.LimitReader(p.Stdout, 4097)) + if err != nil || len(raw) > 4096 { + p.Cancel() + } + waitErr := p.Wait() + var info struct { + Protocol int `json:"protocol"` + Native, Source string + } + if err != nil || waitErr != nil || len(raw) > 4096 || json.Unmarshal(raw, &info) != nil || info.Protocol != 1 || info.Native != SupportedVersion || info.Source != "33b259bbbeb1c16433390869938191d09bdb0680" { + return fmt.Errorf("mcode: workspace companion check failed") + } + return nil +} diff --git a/apps/parsar-daemon/internal/cli/agent_discovery.go b/apps/parsar-daemon/internal/cli/agent_discovery.go index 1a073d6e..4f6a1055 100644 --- a/apps/parsar-daemon/internal/cli/agent_discovery.go +++ b/apps/parsar-daemon/internal/cli/agent_discovery.go @@ -16,12 +16,13 @@ import ( // agentCLIDiscovery is the daemon startup snapshot advertised in heartbeat. type agentCLIDiscovery struct { - ClaudeSDK *claudeSDKDiscovery - ClaudeCode proto.SupportedAgentKind - OpenCode proto.SupportedAgentKind - Codex proto.SupportedAgentKind - Pi proto.SupportedAgentKind - MCode proto.SupportedAgentKind + MCodeWorkspace *mcode.WorkspaceConfig + ClaudeSDK *claudeSDKDiscovery + ClaudeCode proto.SupportedAgentKind + OpenCode proto.SupportedAgentKind + Codex proto.SupportedAgentKind + Pi proto.SupportedAgentKind + MCode proto.SupportedAgentKind } type agentCLIChecks struct { @@ -180,6 +181,7 @@ func discoverAgentCLIs(rc *runContext, profile string, checks agentCLIChecks) (a } out.MCode = discoverMCode(rc, checks.MCode) + discoverMCodeWorkspace(rc, &out) out.ClaudeSDK = discoverClaudeSDK(rc, profile, checks.ClaudeSDK) if !out.ClaudeCode.Available && !out.OpenCode.Available && !out.Codex.Available && !out.Pi.Available && !out.MCode.Available && (out.ClaudeSDK == nil || !out.ClaudeSDK.Info.Available) { diff --git a/apps/parsar-daemon/internal/cli/agent_registration.go b/apps/parsar-daemon/internal/cli/agent_registration.go index 55e589d5..51b7797d 100644 --- a/apps/parsar-daemon/internal/cli/agent_registration.go +++ b/apps/parsar-daemon/internal/cli/agent_registration.go @@ -26,7 +26,12 @@ func registerAgentKinds(registry *agent.Registry, agentCLIs agentCLIDiscovery, s }) } registerProductAgentKind(registry, agentCLIs.Pi, withSkillUploadServer(withCapabilityDownloads(pi.Factory, serverURL), serverURL)) - registerProductAgentKind(registry, agentCLIs.MCode, withSkillUploadServer(withCapabilityDownloads(mcode.Factory, serverURL), serverURL)) + if agentCLIs.MCodeWorkspace != nil { + registry.RegisterKind(agentCLIs.MCode, mcode.Factory) + registry.RegisterPreparation("mcode", true, mcode.NewPreparationFactory(*agentCLIs.MCodeWorkspace)) + } else { + registerProductAgentKind(registry, agentCLIs.MCode, withSkillUploadServer(withCapabilityDownloads(mcode.Factory, serverURL), serverURL)) + } registerClaudeSDK(registry, agentCLIs.ClaudeSDK) } diff --git a/apps/parsar-daemon/internal/cli/mcode_workspace.go b/apps/parsar-daemon/internal/cli/mcode_workspace.go new file mode 100644 index 00000000..bf62d001 --- /dev/null +++ b/apps/parsar-daemon/internal/cli/mcode_workspace.go @@ -0,0 +1,76 @@ +package cli + +import ( + "context" + "fmt" + "os" + "os/exec" + "path/filepath" + + "github.com/MiniMax-AI-Dev/parsar/apps/parsar-daemon/internal/agent/binpath" + "github.com/MiniMax-AI-Dev/parsar/apps/parsar-daemon/internal/agent/mcode" + "github.com/MiniMax-AI-Dev/parsar/apps/parsar-daemon/internal/localworkspace" + "github.com/MiniMax-AI-Dev/parsar/apps/parsar-daemon/internal/paths" +) + +func discoverMCodeWorkspace(rc *runContext, discovery *agentCLIDiscovery) { + mode := os.Getenv("PARSAR_MCODE_WORKSPACE") + if mode == "" { + return + } + fail := func(err error) { + discovery.MCode.Available = false + fmt.Fprintf(rc.stderr, "parsar-daemon: mcode workspace unavailable: %v\n", err) + } + if mode != "managed" || !discovery.MCode.Available || !mcode.SupportsExecution(discovery.MCode.Version) { + fail(fmt.Errorf("managed execution requires the qualified native version and opt-in")) + return + } + binding, err := localworkspace.Load() + if err != nil || binding == nil { + fail(fmt.Errorf("dedicated local Runtime binding required")) + return + } + root, err := paths.Root() + if err != nil { + fail(err) + return + } + node := os.Getenv("PARSAR_MCODE_NODE") + if node == "" { + node = "node" + } + node, err = exec.LookPath(node) + if err != nil { + fail(err) + return + } + node, err = filepath.Abs(node) + if err != nil { + fail(err) + return + } + binary, err := exec.LookPath(binpath.MCode()) + if err != nil { + fail(err) + return + } + binary, err = filepath.Abs(binary) + if err != nil { + fail(err) + return + } + c, err := mcode.ConfigureLocal(binary, node, os.Getenv("PARSAR_MCODE_WORKSPACE_BRIDGE"), root, os.Getenv("PARSAR_RUNTIME_WORKSPACE"), binding.NetworkAccess(), os.Getenv("PARSAR_RUNTIME_STAGING")) + if err == nil { + err = mcode.CheckWorkspace(context.Background(), c) + } + if err != nil { + fail(err) + return + } + discovery.MCodeWorkspace = &c + caps := &discovery.MCode.Capabilities + caps.EnvironmentNone = false + caps.Preparation, caps.LocalEnvironment, caps.LocalEnvironmentNetworkPolicy = true, true, true + caps.WorkspaceReadPreparation = true +} diff --git a/contracts/agents-api/harness-onboarding.md b/contracts/agents-api/harness-onboarding.md index 6a3b6359..60c038fe 100644 --- a/contracts/agents-api/harness-onboarding.md +++ b/contracts/agents-api/harness-onboarding.md @@ -138,3 +138,9 @@ See [the MiniMax Code deployment guide](../../services/agents-api/deploy/mcode/R for setup, real-provider acceptance and limits. Hosted workspace, Files/Artifacts, public functions and MCP are not qualified by the text profile. Native differences remain separately tracked work; they do not require feature equality for onboarding. + +The requested MiniMax Code workspace Runtime is qualified separately in +[MCODE-WORKSPACE-V1-001](mcode-workspace-v1.md). Its explicit scope includes +workspace execution and shared Files/Artifacts, cancellation/recovery and independent +Docker deployment. Text-only qualification is an intermediate milestone for that +scope, not completion of the requested Runtime integration. diff --git a/contracts/agents-api/mcode-workspace-v1.md b/contracts/agents-api/mcode-workspace-v1.md new file mode 100644 index 00000000..45aaf3b9 --- /dev/null +++ b/contracts/agents-api/mcode-workspace-v1.md @@ -0,0 +1,158 @@ +# MiniMax Code workspace Runtime qualification + +Status: implementation, public qualification and independent review passed, +2026-09-20. Baseline main +`55502c87c3f722bbefc5449348309e3fcc03a7ee`. Board batch: +`MCODE-WORKSPACE-V1-001`, under `ENGINE-EXTENSIBILITY-001`. + +## Goal and boundaries + +Complete the requested MiniMax Code Docker workspace loop through the existing +Core/Runtime contract: create a Session, prepare its Environment, execute native +tools, upload/list workspace files, export immutable Artifacts, cancel, reconnect +and continue the exact native history. Prove independent deployment without Parsar. +PR #702 qualified text execution only; it does not complete this goal. + +Core owns the public protocol, authorization and durable resources. The Docker +Provider creates and reclaims one dedicated Runtime; daemon, native harness and +local tools share that Runtime. Native configuration and isolation belong in the +adapter. Reuse preparation ownership, localworkspace binding and common file and +export helpers. Do not add engine-name branches to public handlers, persistence or +scheduling, or implement another model/tool loop. + +This batch excludes E2B, public MCP/functions/Subagents, a general plugin or sandbox +framework, product migration and unrelated refactoring. Existing qualified Codex +and Claude behavior remains protected. Optional feature equality is not required; +the explicitly requested workspace and Files/Artifacts loop is required. + +## Design gate before hosted wiring + +The inspected native source is `MiniMax-AI/minimax-code` revision +`33b259bbbeb1c16433390869938191d09bdb0680`, package 0.4.12. A deterministic +Linux ACP probe returned a private synthetic canary through Read with sandboxing +enabled and `denyRead` configured. The disabled control also returned it. This +probe used no real secrets or real model and is not acceptance evidence. + +The native registry supplies sandbox operations to Bash only; native file tools +execute in the harness process that owns model configuration and history. Docker +alone does not separate those tools from that process's private data. + +A first prototype moved six native tools into an isolated worker. Ordinary tool +operations, disabled networking and detached-child cancellation passed in Docker, +as did real Kimi and MiniMax Write/Read/Bash calls. However, the native process +still auto-started a workspace `.mcp.json` command and followed a `CLAUDE.md` +symlink into private data. Both bypasses were reproduced with synthetic canaries. +Native pre-tool file capture also reads paths before tool execution. The registry +patch is therefore not an adequate authority boundary and will not be shipped. + +The revised candidate retains the published CLI and uses its existing MCP client. +The native process and ACP Session use one private control directory. A single +trusted stdio MCP bridge exposes the original Read, Write, Edit, Bash, Grep and +Glob implementations under workspace-prefixed names, using upstream JSON schemas. +The bridge runs tools in the upstream-vendored Linux sandbox. Only this isolated +worker receives the real workspace as its execution root. Automatic native +project loading remains confined to the private control directory; caller files +cannot configure native processes or redirect privileged instruction reads. + +This uses MCP as an adapter-internal transport, not public MCP feature admission. +Keep native model execution, ACP and history. Reuse native tools rather than +reimplement them. Workspace project instructions can be read through the isolated +tools; do not silently import them into the privileged control process. Preserve +accurate workspace guidance in the trusted tool descriptions. + +Before hosted wiring, prove the published CLI actually selects these tools, +rejects private-file reads, ignores workspace MCP/instruction canaries, enforces +network policy and settles all owned workers on cancellation or transport loss. +Repeat real-provider acceptance against this candidate; earlier prototype results +do not qualify it. If the bridge needs a new model loop or a general compatibility +framework, stop and reassess rather than weakening isolation. + +## Acceptance and stop conditions + +| Area | Required observable result | +| --- | --- | +| Shared lifecycle | Preparation consumes no model input; Start transfers ownership once; cancellation/release settle once after effects stop; events stay ordered. | +| Workspace | Real model reads uploaded input, edits/creates files and runs a native command in the bound workspace. | +| Files and Artifacts | Official SDK and raw HTTP verify actual content, path filtering and pagination; exports remain immutable and scoped to their owner. | +| Recovery | Reconnect and cold continuation retain exact native history and workspace; no duplicate execution; missing or foreign history rejects before model input. | +| Isolation | Tenant/auth checks, daemon/model credentials, native history, staging and cross-Session paths remain protected; tools cannot escape through filesystem or process aliases. | +| Network and cancellation | Each exposed network mode is enforced; cancellation leaves no late file writes or surviving detached tool children. | +| Deployment | Separate Core and execution database with Docker-managed Runtime, no Parsar service or product database dependency. | +| Validation | Targeted/race checks, real Kimi and MiniMax API runs, `make check`, applicable generated artifacts and a fresh independent Astra high blind review. | + +Synthetic fixtures may isolate failures but cannot replace real provider acceptance. +Record exact source/image versions, commands, results and unverified behavior. Keep +credentials in private operator files, outside commits and reports. New mechanisms +must address this batch's demonstrated functional or security risk. + +The batch ends when this loop passes and its bounded PR is merged. Record +nonblocking issues in the board without expanding the batch. Do not close the +parent protocol objective or claim complete official protocol compatibility. + +## Qualification evidence + +The initial qualification candidate uses published CLI 0.4.12, the source revision above, Node.js +22.23.1 and Docker image +`sha256:9bfcf2c3a1bcf2ebf844878897d561dacb6a525c938c5d059645d0f88e1fdb99`. +The npm registry still reported 0.4.12 as latest on 2026-09-20. The Core was built +with `scripts/build-agents-api.sh` and run from a source-free package with its own +PostgreSQL database and credentials. + +| Check | Result | +| --- | --- | +| Official SDK 3.13.0 and raw HTTP, real Kimi, standalone Docker workspace | Passed: Session admission/auth/tenant scope, Files upload/list/filter/order/pages, native input reads, both network policies, protected paths, Core restart, delayed Runtime reconnect, cancellation with no late writes. | +| Public Artifacts, real Kimi and MiniMax independently | Passed: binary/empty/nested/source-file exports, exact content, immutable versions, ownership, restart, cancelled-Turn exclusion, Environment expiry and orphan-free deletion. | +| Core SIGKILL during execution, real Kimi | Passed: failed Turn queries, no automatic/idempotent replay, stopped effects, exact history continuation, retained Items/Artifacts and foreign-Session isolation. | +| Runtime SIGKILL during execution, real MiniMax | Passed the same recovery checks, including continuation that did not execute the interrupted command again. | +| Lifecycle and repository checks | `make check`, targeted Go race tests and the existing published-CLI product ACP regression passed. `make openapi` produced no generated changes; no query/schema change requires sqlc regeneration. | + +Evidence is retained on `zju_a100_2` under +`~/.parsar/remediation/20260919/mcode-workspace/`: `public-hosted-latest.json`, +`public-artifacts-latest.json`, `public-minimax-artifacts-latest.json`, +`public-recovery-latest.json`, `public-minimax-recovery-diagnostic-latest.json`, +`race.log`, `make-check.log` and `product-native-regression.log`. These private +fixtures reuse the fixed official client and existing public Files/Artifacts +acceptance helpers; they do not substitute model responses. A locally cached base +image matching the pinned Node manifest was used with Docker's legacy builder; +only its temporary build context omitted BuildKit chmod syntax. + +Kimi's Runtime-kill continuation did not pass the no-repeat assertion: after a new +input, a new tool-call identity executed the interrupted command again. The +pre-input recovery and idempotency checks passed. Keep the failed evidence and +source inspection confirms ACP preserves native call IDs and the native loop +executes only calls from a fresh model response after the new input. This was a +new model-issued command, not automatic recovery replay. The passing MiniMax run +does not erase the Kimi failure. Model obedience is not an exactly-once execution +guarantee; no model-output patch or public command filter was added. + +The initial-image real MiniMax isolation probe also passed: workspace MCP and +instruction-symlink canaries did not affect the privileged process; native file +tools and `/proc` aliases could not read synthetic credentials/history; visible +process environments contained no privileged values. Missing and foreign native +Session loads rejected before any model request. All task-owned probe containers +were removed. Evidence: `final-isolation/docker-final-isolation-result.json`. + +The final candidate is +`sha256:da8ab6840fe33bf493771419d68fe271ad155ff34f2c258e43bd6cd0a79260ec`. +It directs native temporary files to the existing writable scratch directory and +includes the upstream tool license. The earlier image failed the same native +temporary-file regression; the final image passed under both network policies: +`mktemp`, Node temporary paths, 272 KB Bash output spill and native Read. Private +canaries remained unreadable, real private directories were unchanged, the control +directory stayed read-only, and network/seccomp restrictions remained enforced. +MCP cancellation and EOF stopped detached children with no delayed writes. + +On this final image, real MiniMax public Artifacts and both Core/Runtime SIGKILL +recovery suites passed with no cleanup errors. Full `make check`, focused race +tests and OpenAPI generation also passed after the change. The default full check +skips the opt-in packaged native test; both actual Docker runs above executed it. +The broader initial-image acceptance remains evidence for unchanged public wiring +and isolation, rather than a claim that every suite reran on the final image. +Final evidence: `public-minimax-recovery-latest.json`, +`public-minimax-artifacts-latest.json`, and `proof/temp-regression/`. + +A fresh independent GPT-6 Astra high review of the complete diff found no blocking +issues. Its independent mcode/CLI/engine/execution race tests and companion checks +passed; the reviewer did not rerun the full gate or real-provider deployment. +Those results above were run by the implementation owner. This qualification does +not establish complete protocol compatibility. diff --git a/packages/mcode-harness/README.md b/packages/mcode-harness/README.md new file mode 100644 index 00000000..2c7872e0 --- /dev/null +++ b/packages/mcode-harness/README.md @@ -0,0 +1,54 @@ +# MiniMax Code workspace bridge + +This adapter companion keeps the published MiniMax Code CLI, ACP, model loop and +history. Its trusted MCP server exposes six original native tools inside the +upstream-vendored Linux sandbox. There is no CLI source patch or replacement loop. +Hosted public execution is not qualified by this package alone. + +The harness process and ACP Session use a private control directory. Builtin file +tools are disabled. Only the adapter registers this bridge; callers cannot supply +its command, profile, working directory or environment. Workspace project files +are read through sandboxed tools rather than imported by the privileged harness. +Native diff/undo capture is not provided by this path. Common Files/Artifacts use +the bound public workspace independently of the native control directory. + +`source.json` pins the native tool and sandbox source. The published CLI is a +separate dependency; both artifacts require qualification. On Linux x86_64: + +```sh +MCODE_NATIVE_SOURCE=/absolute/upstream/checkout bash scripts/build-mcode-harness.sh +``` + +This standalone companion uses its own npm lock and is excluded from the root +pnpm workspace. The build archives the exact source revision, bundles its native tools and sandbox +and installs pinned MCP dependencies. It does not build the upstream CLI. Install +the artifact immutably at `/opt/mcode-harness`. The private profile supplies +`workspace`, `scratch`, `protectedDirs` and `network`. Only the +isolated worker receives the real workspace as its tool root. Missing or mismatched +profiles reject; there is no unsandboxed fallback. + +The bridge owns each launcher until exit. MCP cancellation and transport shutdown +stop all owned workers before releasing the bridge. The outer Runtime owns the +native process group. Both boundaries require real Docker cancellation tests. + +Native tool schemas are retained. Text and image results use standard MCP content; +video results reject explicitly. The published CLI may add task/skill utility tools; +qualification must inspect the actual inventory rather than assume exactly six. + +See [workspace qualification](../../contracts/agents-api/mcode-workspace-v1.md) for +the required tests and stopping conditions. Synthetic isolation probes and native +model runs do not complete public Files/Artifacts or independent Core acceptance. + +For the packaged Linux regression, provide an operator-owned private profile and +artifact directory, then run `native.test.mjs` inside the qualified Docker Runtime: + +```sh +PARSAR_MCODE_NATIVE_PROFILE=/absolute/private-profile.json \ +PARSAR_MCODE_NATIVE_ARTIFACT=/opt/mcode-harness \ +node --test packages/mcode-harness/native.test.mjs +``` + +Run once for each supported network policy. It verifies writable native TMPDIR, +large Bash output retention and subsequent native Read. The ordinary repository +gate skips this case without those explicit inputs; it cannot replace Docker +isolation, cancellation or real-model acceptance. diff --git a/packages/mcode-harness/bridge.mjs b/packages/mcode-harness/bridge.mjs new file mode 100644 index 00000000..2f986f9e --- /dev/null +++ b/packages/mcode-harness/bridge.mjs @@ -0,0 +1,34 @@ +import { Server } from '@modelcontextprotocol/sdk/server/index.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; +import { readFileSync } from 'node:fs'; +import { isAbsolute } from 'node:path'; +import { ToolExecutor } from './tool-executor.mjs'; + +const profile = process.argv[2]; +if (!profile || !isAbsolute(profile)) throw new Error('Private workspace profile is required'); +const definitions = JSON.parse(readFileSync(new URL('./dist/tools.json', import.meta.url), 'utf8')); +const tools = new Map(definitions.map(tool => ['workspace_' + tool.name, tool])); +const executor = new ToolExecutor(profile); +const server = new Server({ name: 'parsar-workspace', version: '1' }, { capabilities: { tools: {} } }); +server.setRequestHandler(ListToolsRequestSchema, async () => ({ + tools: [...tools].map(([name, tool]) => ({ ...tool, name, + description: 'Bound working directory: /workspace. ' + tool.description })), +})); +server.setRequestHandler(CallToolRequestSchema, async (request, extra) => { + const tool = tools.get(request.params.name); + if (!tool) throw new Error('Unknown workspace tool'); + const result = await executor.execute(tool.name, request.params.arguments, extra.signal); + if (result.content.some(item => item.type !== 'text' && item.type !== 'image')) + return { isError: true, content: [{ type: 'text', text: 'This workspace transport supports text and image results only.' }] }; + return { isError: result.isError === true, + content: result.content.length ? result.content : [{ type: 'text', text: result.text }] }; +}); + +let shutdown; +const close = () => shutdown ??= executor.close().then(() => server.close()); +server.onclose = () => { void close(); }; +process.stdin.on('end', () => { void close(); }); +process.on('SIGTERM', () => { void close(); }); +process.on('SIGINT', () => { void close(); }); +await server.connect(new StdioServerTransport()); diff --git a/packages/mcode-harness/build-sandbox.mjs b/packages/mcode-harness/build-sandbox.mjs new file mode 100644 index 00000000..25deca88 --- /dev/null +++ b/packages/mcode-harness/build-sandbox.mjs @@ -0,0 +1,18 @@ +import { build } from 'esbuild'; +import { mkdirSync, cpSync, writeFileSync } from 'node:fs'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +const here = dirname(fileURLToPath(import.meta.url)); +const source = process.env.MCODE_SOURCE; +if (!source) throw new Error('MCODE_SOURCE is required'); +mkdirSync(resolve(here, 'dist'), { recursive: true }); +const result = await build({ entryPoints: [resolve(here, 'sandbox-entry.ts')], + outfile: resolve(here, 'dist/sandbox.mjs'), bundle: true, platform: 'node', format: 'esm', + target: 'node22', nodePaths: [resolve(here, 'node_modules')], metafile: true, + plugins: [{ name: 'sandbox-source', setup(b) { b.onResolve({ filter: /^@sandbox\// }, () => + ({ path: resolve(source, 'third_party/sandbox-runtime/src/sandbox/sandbox-manager.ts') })); } }], + banner: { js: 'import { createRequire as srtCreateRequire } from "node:module"; const require = srtCreateRequire(import.meta.url);' }, + logLevel: 'info' }); +cpSync(resolve(source, 'third_party/sandbox-runtime/vendor/seccomp'), resolve(here, 'dist/vendor/seccomp'), { recursive: true }); +cpSync(resolve(source, 'third_party/sandbox-runtime/vendor/java-proxy-agent'), resolve(here, 'dist/vendor/java-proxy-agent'), { recursive: true }); +writeFileSync(resolve(here, 'dist/sandbox-metafile.json'), JSON.stringify(result.metafile, null, 2) + '\n'); diff --git a/packages/mcode-harness/build.mjs b/packages/mcode-harness/build.mjs new file mode 100644 index 00000000..12d6b4a4 --- /dev/null +++ b/packages/mcode-harness/build.mjs @@ -0,0 +1,31 @@ +import { build } from 'esbuild'; +import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +const here = dirname(fileURLToPath(import.meta.url)); +const source = process.env.MCODE_SOURCE; +if (!source) throw new Error('MCODE_SOURCE is required'); +const revision = '33b259bbbeb1c16433390869938191d09bdb0680'; +if (readFileSync(resolve(source, '.parsar-source-revision'), 'utf8').trim() !== revision) + throw new Error('source revision mismatch'); +const paths = JSON.parse(readFileSync(resolve(source, 'tsconfig.standalone.json'))).compilerOptions.paths; +const plugin = { name: 'pinned-native-source', setup(builder) { + builder.onResolve({ filter: /^[^./]/ }, ({ path }) => { + if (path === '@earendil-works/pi-coding-agent') return { path: resolve(here, 'native-pi-tools.ts') }; + if (path.startsWith('@native/')) return { path: resolve(source, 'packages/agent-tools/src/desktop', path.slice(8) + '.ts') }; + if (path.startsWith('@pi/')) return { path: resolve(source, 'third_party/pi-mono/packages/coding-agent/src', path.slice(4) + '.ts') }; + if (paths[path]) return { path: resolve(source, paths[path][0]) }; + }); +}}; +mkdirSync(resolve(here, 'dist'), { recursive: true }); +const result = await build({ entryPoints: [resolve(here, 'worker.ts')], outfile: resolve(here, 'dist/worker.mjs'), + bundle: true, platform: 'node', format: 'esm', target: 'node22', packages: 'external', + plugins: [plugin], tsconfig: resolve(source, 'tsconfig.standalone.json'), metafile: true, + banner: { js: 'import { createRequire as workerCreateRequire } from "node:module"; const require = workerCreateRequire(import.meta.url);' }, + logLevel: 'info' }); +writeFileSync(resolve(here, 'dist/metafile.json'), JSON.stringify(result.metafile, null, 2) + '\n'); +const external = [...new Set(Object.values(result.metafile.outputs).flatMap(o => o.imports) + .filter(i => i.external && !i.path.startsWith('node:')).map(i => i.path))]; +writeFileSync(resolve(here, 'dist/build-evidence.json'), JSON.stringify({ revision, + sourceFiles: Object.keys(result.metafile.inputs).length, external }, null, 2) + '\n'); +console.log(JSON.stringify({ sourceFiles: Object.keys(result.metafile.inputs).length, external })); diff --git a/packages/mcode-harness/check.mjs b/packages/mcode-harness/check.mjs new file mode 100644 index 00000000..4d2cd1b9 --- /dev/null +++ b/packages/mcode-harness/check.mjs @@ -0,0 +1,14 @@ +import '@modelcontextprotocol/sdk/server/index.js'; +import { accessSync, readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { execFileSync } from 'node:child_process'; + +if (process.platform !== 'linux' || process.arch !== 'x64') throw new Error('Linux x86_64 is required'); +for (const file of ['bridge.mjs', 'launch.mjs', 'tool-executor.mjs', 'dist/worker.mjs', + 'dist/sandbox.mjs', 'dist/vendor/seccomp/x64/apply-seccomp']) accessSync(new URL(file, import.meta.url)); +for (const command of ['bwrap', 'socat', 'rg']) execFileSync('which', [command], { stdio: 'ignore' }); +const pin = JSON.parse(readFileSync(new URL('source.json', import.meta.url))); +const tools = JSON.parse(execFileSync(process.execPath, + [fileURLToPath(new URL('dist/worker.mjs', import.meta.url)), '/workspace', '--describe'], { maxBuffer: 1024 * 1024 })); +if (tools.map(t => t.name).sort().join(',') !== 'bash,edit,glob,grep,read,write') throw new Error('Native tool inventory mismatch'); +process.stdout.write(JSON.stringify({ protocol: 1, native: pin.version, source: pin.revision }) + '\n'); diff --git a/packages/mcode-harness/launch.mjs b/packages/mcode-harness/launch.mjs new file mode 100644 index 00000000..cd39c492 --- /dev/null +++ b/packages/mcode-harness/launch.mjs @@ -0,0 +1,33 @@ +import { SandboxManager } from './dist/sandbox.mjs'; +import { spawn } from 'node:child_process'; +import { readFileSync, mkdirSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +const here = dirname(fileURLToPath(import.meta.url)); +const profile = JSON.parse(readFileSync(process.argv[2], 'utf8')); +if (profile.workspace !== process.argv[3] || profile.workspace !== '/workspace') + throw new Error('Workspace profile does not match execution binding'); +if (!['disabled','enabled'].includes(profile.network)) throw new Error('Invalid network policy'); +const baseEnv = {PATH:'/usr/local/bin:/usr/bin:/bin',HOME:profile.scratch,TMPDIR:profile.scratch,LANG:'C.UTF-8'}; +let child; +let cancelled=false; +const cancel=()=>{cancelled=true;child?.kill('SIGKILL');}; +process.on('SIGTERM',cancel);process.on('SIGINT',cancel); +try { + mkdirSync(profile.scratch,{recursive:true}); + await SandboxManager.initialize({ + network:{allowedDomains:[],deniedDomains:profile.network==='disabled'?['*']:[],allowAll:profile.network==='enabled'}, + filesystem:{denyRead:profile.protectedDirs,allowWrite:[profile.workspace,profile.scratch],denyWrite:[]}, + seccomp:{applyPath:join(here,'dist/vendor/seccomp/x64/apply-seccomp')}, + },undefined,false); + const quote=s=>"'"+s.replaceAll("'","'\\''")+"'"; + const command=[process.execPath,join(here,'dist/worker.mjs'),profile.workspace].map(quote).join(' '); + const wrapped=await SandboxManager.wrapWithSandbox(command,'/bin/bash',undefined,undefined,{baseEnv,sandboxTempDir:profile.scratch}); + if (cancelled) throw new Error('Cancelled before workspace tool start'); + child=spawn('/bin/bash',['-c','exec '+wrapped],{cwd:profile.workspace,env:baseEnv,stdio:['pipe','pipe','pipe']}); + process.stdin.pipe(child.stdin);child.stdout.pipe(process.stdout);child.stderr.pipe(process.stderr); + child.stdin.on('error',()=>cancel()); + const status=await new Promise((resolve,reject)=>{child.on('error',reject);child.on('close',resolve);}); + process.exitCode=cancelled?1:(status??1); +} catch(error) {process.stderr.write(String(error)+'\n');process.exitCode=1;} +finally {await SandboxManager.reset();} diff --git a/packages/mcode-harness/native-pi-tools.ts b/packages/mcode-harness/native-pi-tools.ts new file mode 100644 index 00000000..dadd17e4 --- /dev/null +++ b/packages/mcode-harness/native-pi-tools.ts @@ -0,0 +1,5 @@ +export { createReadTool } from '@pi/core/tools/read'; +export { createWriteTool } from '@pi/core/tools/write'; +export { createEditTool } from '@pi/core/tools/edit'; +export { createBashTool } from '@pi/core/tools/bash'; +export { getShellConfig } from '@pi/utils/shell'; diff --git a/packages/mcode-harness/native.test.mjs b/packages/mcode-harness/native.test.mjs new file mode 100644 index 00000000..7f922cb7 --- /dev/null +++ b/packages/mcode-harness/native.test.mjs @@ -0,0 +1,36 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { readFile } from 'node:fs/promises'; +import { isAbsolute, join, sep } from 'node:path'; +import { pathToFileURL } from 'node:url'; + +const profile = process.env.PARSAR_MCODE_NATIVE_PROFILE; +const artifact = process.env.PARSAR_MCODE_NATIVE_ARTIFACT; + +test('packaged native tools use writable scratch and retain large output', { + skip: !profile || !artifact, + timeout: 30_000, +}, async t => { + assert.ok(isAbsolute(profile) && isAbsolute(artifact)); + const config = JSON.parse(await readFile(profile, 'utf8')); + const { ToolExecutor } = await import(pathToFileURL(join(artifact, 'tool-executor.mjs'))); + const executor = new ToolExecutor(profile); + t.after(() => executor.close()); + const temporary = await executor.execute('bash', { + command: 'node -p "require(\'os\').tmpdir()"; f=$(mktemp) && printf TEMP_OK > "$f" && cat "$f" && rm "$f"', + }); + assert.notEqual(temporary.isError, true); + assert.ok(temporary.text.includes(config.scratch)); + assert.ok(temporary.text.includes('TEMP_OK')); + const output = await executor.execute('bash', { + command: 'python3 -c "print((\'native-spill-line\' + chr(10)) * 16000, end=\'\')"', + }); + assert.notEqual(output.isError, true); + const full = output.details?.fullOutputPath; + assert.equal(typeof full, 'string'); + assert.ok(full.startsWith(config.scratch + sep)); + assert.equal(await readFile(full, 'utf8'), 'native-spill-line\n'.repeat(16000)); + const read = await executor.execute('read', { path: full, offset: 1, limit: 1 }); + assert.notEqual(read.isError, true); + assert.ok(read.text.includes('native-spill-line')); +}); diff --git a/packages/mcode-harness/package-lock.json b/packages/mcode-harness/package-lock.json new file mode 100644 index 00000000..712c13f4 --- /dev/null +++ b/packages/mcode-harness/package-lock.json @@ -0,0 +1,1841 @@ +{ + "name": "parsar-mcode-harness", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "parsar-mcode-harness", + "dependencies": { + "@modelcontextprotocol/sdk": "1.30.0", + "@pondwader/socks5-server": "1.0.10", + "@silvia-odwyer/photon-node": "0.3.4", + "@sinclair/typebox": "0.34.52", + "chalk": "5.6.2", + "cross-spawn": "7.0.6", + "diff": "8.0.4", + "get-east-asian-width": "1.6.0", + "highlight.js": "10.7.3", + "marked": "18.0.12", + "minimatch": "10.2.6", + "node-forge": "1.4.0", + "typebox": "1.1.38", + "unpdf": "1.8.1", + "zod": "3.25.76" + }, + "devDependencies": { + "esbuild": "0.28.2" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@hono/node-server": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.1.tgz", + "integrity": "sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz", + "integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9 || ^2.0.5", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@pondwader/socks5-server": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@pondwader/socks5-server/-/socks5-server-1.0.10.tgz", + "integrity": "sha512-bQY06wzzR8D2+vVCUoBsr5QS2U6UgPUQRmErNwtsuI6vLcyRKkafjkr3KxbtGFf9aBBIV2mcvlsKD1UYaIV+sg==", + "license": "MIT" + }, + "node_modules/@silvia-odwyer/photon-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz", + "integrity": "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA==", + "license": "Apache-2.0" + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "license": "MIT" + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz", + "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.7.0.tgz", + "integrity": "sha512-hOwV7WOxXfjRpAM1DSJWZDXx3GhplwD8IfwuwvogD8i1Qnkgosw/H45s4ZnFAUHDAhPjlY9hLBvJhKmGMyY26g==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "ip-address": "^10.2.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/hono": { + "version": "4.13.8", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.8.tgz", + "integrity": "sha512-/Gng7NfoykZl2pjukW5Z6+8Yxm3BPRf86GTbQnt0SbySkvax4fyL4H3HhY1cCpBGmiW9XDRFzRV+CXK2W8QudQ==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.7.2", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.2.tgz", + "integrity": "sha512-7H/2gFSIitxc0hG3nOI1glS8QLo/EHBFFLk8vEUjXY/xu0AdL8jZ9U1IzO2PUm0d2D/ofQcAifb0g6OBkt8U7w==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jose": { + "version": "6.2.12", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.12.tgz", + "integrity": "sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/marked": { + "version": "18.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.12.tgz", + "integrity": "sha512-LEm4ga2YeI2T3GVHj9b0BaDPPk93LLTHMFeMyQbNIzPxc8vCI0y/scy0ZA6z6lXKyT9j9Nhl/OC6ZYKYGuFScA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.1.0.tgz", + "integrity": "sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==", + "license": "MIT", + "dependencies": { + "content-type": "^2.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/negotiator/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/node-forge": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz", + "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/qs": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", + "license": "BSD-3-Clause", + "dependencies": { + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/typebox": { + "version": "1.1.38", + "resolved": "https://registry.npmjs.org/typebox/-/typebox-1.1.38.tgz", + "integrity": "sha512-pZ0aQPmMmXoUvSbeuWf/Hzsc+avNw/Zd6VeE8CFgkVGWyuHPJvqeJJDeJqLve+K70LvjYIoleGcoJHPT17cWoA==", + "license": "MIT" + }, + "node_modules/unpdf": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/unpdf/-/unpdf-1.8.1.tgz", + "integrity": "sha512-xkURhy2SoGpOIH0a1gLHNkASPIQYonadDJs2AQwPEfUakafeD9EA1WTWWsaR++gfTCXJpV27W7tU1nXuk82UKQ==", + "license": "MIT", + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "@napi-rs/canvas": "^0.1.69 || ^1.0.0" + }, + "peerDependenciesMeta": { + "@napi-rs/canvas": { + "optional": true + } + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + } + } +} diff --git a/packages/mcode-harness/package.json b/packages/mcode-harness/package.json new file mode 100644 index 00000000..0280f4c2 --- /dev/null +++ b/packages/mcode-harness/package.json @@ -0,0 +1,28 @@ +{ + "name": "parsar-mcode-harness", + "private": true, + "type": "module", + "devDependencies": { + "esbuild": "0.28.2" + }, + "dependencies": { + "@pondwader/socks5-server": "1.0.10", + "@silvia-odwyer/photon-node": "0.3.4", + "@sinclair/typebox": "0.34.52", + "chalk": "5.6.2", + "cross-spawn": "7.0.6", + "diff": "8.0.4", + "get-east-asian-width": "1.6.0", + "highlight.js": "10.7.3", + "marked": "18.0.12", + "minimatch": "10.2.6", + "node-forge": "1.4.0", + "typebox": "1.1.38", + "unpdf": "1.8.1", + "zod": "3.25.76", + "@modelcontextprotocol/sdk": "1.30.0" + }, + "scripts": { + "test": "node --test *.test.mjs" + } +} diff --git a/packages/mcode-harness/sandbox-entry.ts b/packages/mcode-harness/sandbox-entry.ts new file mode 100644 index 00000000..75c6242d --- /dev/null +++ b/packages/mcode-harness/sandbox-entry.ts @@ -0,0 +1 @@ +export { SandboxManager } from '@sandbox/sandbox-manager'; diff --git a/packages/mcode-harness/source.json b/packages/mcode-harness/source.json new file mode 100644 index 00000000..13236044 --- /dev/null +++ b/packages/mcode-harness/source.json @@ -0,0 +1,5 @@ +{ + "repository": "https://github.com/MiniMax-AI/minimax-code", + "revision": "33b259bbbeb1c16433390869938191d09bdb0680", + "version": "0.4.12" +} diff --git a/packages/mcode-harness/tool-executor.mjs b/packages/mcode-harness/tool-executor.mjs new file mode 100644 index 00000000..db0e2e36 --- /dev/null +++ b/packages/mcode-harness/tool-executor.mjs @@ -0,0 +1,65 @@ +import { spawn } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; + +const limit = 16 * 1024 * 1024; +const launcher = fileURLToPath(new URL('./launch.mjs', import.meta.url)); + +// One bridge owns every launcher until its stdio and sandbox have settled. +export class ToolExecutor { + #calls = new Set(); + #closed = false; + + constructor(profile, entrypoint = launcher) { + this.profile = profile; + this.entrypoint = entrypoint; + } + + async execute(tool, input, signal) { + if (this.#closed) throw new Error('Workspace transport is closed'); + signal?.throwIfAborted(); + const request = JSON.stringify({ tool, input }); + if (Buffer.byteLength(request) > limit) throw new Error('Workspace tool input exceeds limit'); + const child = spawn(process.execPath, [this.entrypoint, this.profile, '/workspace'], { + env: { PATH: '/usr/local/bin:/usr/bin:/bin', HOME: '/tmp', LANG: 'C.UTF-8' }, + stdio: ['pipe', 'pipe', 'pipe'], + }); + const call = { stop: () => child.kill('SIGTERM') }; + this.#calls.add(call); + let output = Buffer.alloc(0); + let failure; + signal?.addEventListener('abort', call.stop, { once: true }); + if (signal?.aborted) call.stop(); + child.on('error', error => { failure = error; }); + child.stdin.on('error', error => { failure = error; call.stop(); }); + child.stderr.resume(); + child.stdout.on('data', chunk => { + if (output.length + chunk.length > limit) { + failure = new Error('Workspace tool output exceeds limit'); + call.stop(); + } else output = Buffer.concat([output, chunk]); + }); + call.settled = new Promise(resolve => child.on('close', resolve)); + child.stdin.end(request); + try { + const code = await call.settled; + if (failure) throw failure; + signal?.throwIfAborted(); + if (this.#closed) throw new Error('Workspace transport is closed'); + const result = JSON.parse(output.toString('utf8')); + if (result.tool_name !== tool || typeof result.text !== 'string' || !Array.isArray(result.content)) + throw new Error('Invalid native workspace tool result'); + if (code !== 0 && result.isError !== true) throw new Error('Workspace tool execution failed'); + return result; + } finally { + signal?.removeEventListener('abort', call.stop); + this.#calls.delete(call); + } + } + + async close() { + this.#closed = true; + const calls = [...this.#calls]; + for (const call of calls) call.stop(); + await Promise.all(calls.map(call => call.settled)); + } +} diff --git a/packages/mcode-harness/tool-executor.test.mjs b/packages/mcode-harness/tool-executor.test.mjs new file mode 100644 index 00000000..e87ab353 --- /dev/null +++ b/packages/mcode-harness/tool-executor.test.mjs @@ -0,0 +1,64 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { mkdtemp, mkdir, writeFile, rm, access } from 'node:fs/promises'; +import { homedir } from 'node:os'; +import { join } from 'node:path'; +import { setTimeout as delay } from 'node:timers/promises'; +import { ToolExecutor } from './tool-executor.mjs'; + +async function fixture(t, body) { + const root = join(homedir(), '.parsar', 'tests'); + await mkdir(root, { recursive: true }); + const dir = await mkdtemp(join(root, 'mcode-worker-')); + t.after(() => rm(dir, { recursive: true, force: true })); + const script = join(dir, 'launcher.mjs'); + await writeFile(script, body); + const executor = new ToolExecutor(dir, script); + t.after(() => executor.close()); + return { dir, executor }; +} + +test('returns the original native result, including a native tool error', async t => { + const { executor } = await fixture(t, ` + process.stdin.resume(); process.stdin.on('end', () => { + console.log(JSON.stringify({tool_name:'read',text:'denied',content:[],isError:true})); + process.exitCode=1; + });`); + assert.deepEqual(await executor.execute('read', { path: 'private' }), { + tool_name: 'read', text: 'denied', content: [], isError: true, + }); +}); + +test('rejects a result attributed to a different native tool', async t => { + const { executor } = await fixture(t, ` + process.stdin.resume(); process.stdin.on('end', () => + console.log(JSON.stringify({tool_name:'write',text:'wrong',content:[]})));`); + await assert.rejects(executor.execute('read', {}), /Invalid native/); +}); + +for (const shutdown of ['cancel', 'transport close']) { + test(`${shutdown} waits for worker exit and prevents late effects`, async t => { + const { dir, executor } = await fixture(t, ` + import { writeFileSync } from 'node:fs'; + process.stdin.resume(); + process.stdin.on('end', () => { + writeFileSync(process.argv[2]+'/ready','1'); + setTimeout(() => writeFileSync(process.argv[2]+'/late','1'),500); + });`); + const controller = new AbortController(); + const failed = assert.rejects(executor.execute('bash', {}, controller.signal)); + let ready = false; + for (let i = 0; i < 100; i++) { + try { await access(join(dir, 'ready')); ready = true; break; } + catch { await delay(10); } + } + assert.equal(ready, true, 'fixture worker started'); + if (shutdown === 'cancel') controller.abort(); + else await executor.close(); + await failed; + await delay(550); + await assert.rejects(access(join(dir, 'late')), { code: 'ENOENT' }); + await executor.close(); + await assert.rejects(executor.execute('read', {}), /transport is closed/); + }); +} diff --git a/packages/mcode-harness/worker.ts b/packages/mcode-harness/worker.ts new file mode 100644 index 00000000..612e6418 --- /dev/null +++ b/packages/mcode-harness/worker.ts @@ -0,0 +1,32 @@ +import { LocalReadTool, LocalWriteTool, LocalEditTool, LocalBashTool } from '@native/local-pi-tools'; +import { LocalGrepTool } from '@native/local-grep'; +import { LocalGlobTool } from '@native/local-glob'; +import { toRuntimeTool, isRuntimeToolInputValid } from '@mavis/agent-core/tools'; +import { readFileSync } from 'node:fs'; +import { isAbsolute } from 'node:path'; + +const root = process.argv[2]; +if (!root || !isAbsolute(root)) throw new Error('absolute workspace argument required'); +const tools = [new LocalReadTool(root), new LocalWriteTool(root), new LocalEditTool(root), + new LocalBashTool(root, undefined, { mode: 'off' }), new LocalGrepTool(root), new LocalGlobTool(root)] + .map(toRuntimeTool); +if (process.argv[3] === '--describe') { + process.stdout.write(JSON.stringify(tools.map(t => ({ name: t.def.name, + description: t.def.description, inputSchema: t.def.schema }))) + '\n'); + process.exit(0); +} +const request = JSON.parse(readFileSync(0, 'utf8')); +const tool = tools.find(value => value.def.name === request.tool); +if (!tool || !request.input || typeof request.input !== 'object' || Array.isArray(request.input) || + !isRuntimeToolInputValid(tools, request.tool, request.input)) + throw new Error('invalid tool request'); +const context = { sessionId: 'worker', turnId: 'call', allowBashAutoPromotion: false, + canConsumeBackgroundBashOutput: false }; +try { + const result = await tool.impl.execute(context, request.input); + process.stdout.write(JSON.stringify(result) + '\n'); +} catch (error) { + process.stdout.write(JSON.stringify({ tool_name: request.tool, isError: true, + text: error instanceof Error ? error.message : String(error), content: [] }) + '\n'); + process.exitCode = 1; +} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index e5629c49..96f84780 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,8 @@ packages: - "apps/*" - "packages/*" + # The native companion is installed from its own npm lock in the Linux artifact build. + - "!packages/mcode-harness" allowBuilds: # Vite requires esbuild's platform-binary installer. Keep the allowlist # explicit so supply-chain-enforcing pnpm runtimes can run project checks. diff --git a/scripts/build-mcode-harness.sh b/scripts/build-mcode-harness.sh new file mode 100644 index 00000000..e0c0a477 --- /dev/null +++ b/scripts/build-mcode-harness.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +package="$repo_root/packages/mcode-harness" +source="${MCODE_NATIVE_SOURCE:?Set MCODE_NATIVE_SOURCE to the pinned upstream checkout}" +if [[ "$(uname -s)" != Linux || "$(uname -m)" != x86_64 ]]; then + printf 'Build the MiniMax Code Runtime artifact on Linux x86_64\n' >&2 + exit 1 +fi +root="$HOME/.parsar/build" +mkdir -p "$root" +context="$(mktemp -d "$root/mcode-build.XXXXXX")" +trap 'rm -rf "$context"' EXIT +revision="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["revision"])' "$package/source.json")" +if [[ "$(git -C "$source" rev-parse HEAD)" != "$revision" ]]; then + printf 'MiniMax Code source revision does not match source.json\n' >&2 + exit 1 +fi +mkdir "$context/upstream" +git -C "$source" archive "$revision" | tar -x -C "$context/upstream" +printf '%s\n' "$revision" > "$context/upstream/.parsar-source-revision" +cp "$package/"*.mjs "$package/"*.ts "$package/"*.json "$context/" +( + cd "$context" + npm ci --no-audit --no-fund + MCODE_SOURCE="$context/upstream" node build.mjs + MCODE_SOURCE="$context/upstream" node build-sandbox.mjs + node dist/worker.mjs /workspace --describe > dist/tools.json + npm prune --omit=dev --no-audit --no-fund +) +artifact="$context/artifact" +mkdir "$artifact" +cp -R "$context/dist" "$context/node_modules" "$artifact/" +cp "$package/launch.mjs" "$package/bridge.mjs" "$package/check.mjs" "$package/tool-executor.mjs" "$package/source.json" "$artifact/" +cp "$context/upstream/LICENSE" "$artifact/UPSTREAM_LICENSE" +cp "$context/upstream/third_party/sandbox-runtime/LICENSE" "$artifact/SANDBOX_LICENSE" +cp "$context/upstream/third_party/pi-mono/LICENSE" "$artifact/PI_LICENSE" +python3 - "$artifact" "$package" <<'PY' +import hashlib,json,pathlib,sys +artifact,package=map(pathlib.Path,sys.argv[1:]) +pin=json.loads((package/'source.json').read_text()) +pin['files']={str(p.relative_to(artifact)):hashlib.sha256(p.read_bytes()).hexdigest() + for p in sorted(artifact.rglob('*')) if p.is_file()} +(artifact/'provenance.json').write_text(json.dumps(pin,indent=2)+'\n') +PY +destination="$root/mcode-harness-$(date +%Y%m%d%H%M%S)" +mv "$artifact" "$destination" +printf 'MiniMax Code Runtime artifact: %s\n' "$destination" diff --git a/scripts/build-mcode-runtime.sh b/scripts/build-mcode-runtime.sh new file mode 100644 index 00000000..efa111a8 --- /dev/null +++ b/scripts/build-mcode-runtime.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +runtime_root="${PARSAR_HOME:-$HOME/.parsar}" +output="${AGENTS_RUNTIME_BUILD_DIR:-$runtime_root/build/mcode-runtime}" +native="${MCODE_CLI_DIR:?Set MCODE_CLI_DIR to the installed published package directory}" +companion="${MCODE_HARNESS_BUILD_DIR:?Set MCODE_HARNESS_BUILD_DIR to the built companion}" +helpers="${AGENTS_EXECUTOR_BUILD_DIR:-$runtime_root/build/agents-executor}" +for directory in "$runtime_root" "$output" "$native" "$companion" "$helpers"; do + [[ "$directory" == /* ]] || { printf 'Absolute build directories are required\n' >&2; exit 1; } +done +test -f "$companion/provenance.json" +test "$(node "$native/cli.js" --version)" = 0.4.12 +mkdir -p "$runtime_root/cache/agents-runtime-builds" +context="$(mktemp -d "$runtime_root/cache/agents-runtime-builds/mcode.XXXXXX")" +trap 'rm -rf "$context"' EXIT +mkdir "$context/mcode" "$context/mcode-harness" +cp -RL "$native/." "$context/mcode/" +cp -R "$companion/." "$context/mcode-harness/" +( + cd "$repo_root" + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -mod=readonly -trimpath \ + -o "$context/parsar-daemon" ./apps/parsar-daemon/cmd/parsar-daemon +) +for helper in agents-api-codex-directory agents-api-codex-write agents-api-workspace-export; do + test -x "$helpers/$helper" + cp "$helpers/$helper" "$context/" +done +cp "$repo_root/services/agents-api/deploy/mcode/Dockerfile" "$context/Dockerfile" +mkdir -p "$output" +cp -R "$context/." "$output/" +printf 'MiniMax Code Runtime image context: %s\n' "$output" diff --git a/services/agents-api/deploy/mcode/Dockerfile b/services/agents-api/deploy/mcode/Dockerfile new file mode 100644 index 00000000..306b4dd4 --- /dev/null +++ b/services/agents-api/deploy/mcode/Dockerfile @@ -0,0 +1,26 @@ +# Build context contains the daemon, shared helpers, published CLI and tool companion. +FROM node:22.23.1-bookworm-slim@sha256:8607a9064d4a571140998ae9e52a3b3fcf9cff361d04642d5971e6cd76d39e27 +USER root +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates bash git python3 ripgrep bubblewrap socat \ + && rm -rf /var/lib/apt/lists/* \ + && mkdir -p /environment/workspace /workspace /home/runtime +COPY --chmod=0555 parsar-daemon agents-api-codex-directory agents-api-codex-write agents-api-workspace-export /usr/local/bin/ +COPY mcode /opt/mcode +COPY mcode-harness /opt/mcode-harness +ENV HOME=/home/runtime PARSAR_HOME=/home/runtime/.parsar \ + PARSAR_MCODE_NODE=/usr/local/bin/node \ + PARSAR_MCODE_BIN=/opt/mcode/cli.js \ + PARSAR_MCODE_WORKSPACE_BRIDGE=/opt/mcode-harness/bridge.mjs \ + PARSAR_MCODE_AGENTS_API=1 \ + PARSAR_MCODE_WORKSPACE=managed \ + PARSAR_RUNTIME_WORKSPACE=/environment/workspace \ + PARSAR_RUNTIME_DIRECTORY_HELPER=/usr/local/bin/agents-api-codex-directory \ + PARSAR_RUNTIME_WRITE_HELPER=/usr/local/bin/agents-api-codex-write \ + PARSAR_RUNTIME_EXPORT_HELPER=/usr/local/bin/agents-api-workspace-export \ + PARSAR_RUNTIME_STAGING=/environment/staging +USER 1000:1000 +RUN node /opt/mcode-harness/check.mjs && /opt/mcode/cli.js --version +WORKDIR /environment/workspace +ENTRYPOINT ["/usr/local/bin/parsar-daemon"] +CMD ["connect", "--profile", "default"] diff --git a/services/agents-api/deploy/mcode/README.md b/services/agents-api/deploy/mcode/README.md index cd7c1069..e48ef640 100644 --- a/services/agents-api/deploy/mcode/README.md +++ b/services/agents-api/deploy/mcode/README.md @@ -1,14 +1,18 @@ -# MiniMax Code text Runtime +# MiniMax Code Runtime -This profile targets `environment:none` through the same Core/daemon execution -contract as the other engines. It has no public workspace, Files, Artifacts, MCP, -function calls, image input or native Subagent execution. Those are separate -qualification tasks, not requirements to match Codex or Claude. +MiniMax Code uses the same Core/Runtime execution contract as the other engines. +The text profile supports `environment:none`; the dedicated Docker profile adds +workspace execution and the shared Files/Artifacts path. See +[workspace qualification](../../../../contracts/agents-api/mcode-workspace-v1.md) +for exact acceptance evidence and limits. +Public MCP/functions, image input and native Subagent execution remain outside +this batch. ## Pinned prerequisite Use the official [`@minimax-ai/code`](https://github.com/MiniMax-AI/minimax-code) -package version **0.4.12**, with Node.js 22.22.x and its native SQLite dependency. +package version **0.4.12**, with Node.js 22.x and its native SQLite dependency. The Docker image pins Node.js +22.23.1; the text fixture used 22.22.0. The inspected upstream source is `33b259bbbeb1c16433390869938191d09bdb0680`. Install outside the checkout, under a private operator directory in `~/.parsar/`. Check the native install succeeds and `mcode --version` reports exactly 0.4.12. @@ -21,6 +25,38 @@ this is not a new public enrollment API or official `self_hosted` implementation Set `AGENTS_API_ENGINE=mcode` in the independent Core deployment. Existing Sessions retain their engine. Do not expose a new public harness selector. +## Docker workspace + +Build the shared workspace helpers, install the published CLI into a private +operator directory, and build the companion from the pinned native source: + +```sh +MCODE_NATIVE_SOURCE=/absolute/minimax-code bash scripts/build-mcode-harness.sh +MCODE_CLI_DIR=/absolute/published-package \ +MCODE_HARNESS_BUILD_DIR=/absolute/built-companion \ +bash scripts/build-mcode-runtime.sh +docker build --platform linux/amd64 -t agents-runtime:mcode \ + "${PARSAR_HOME:-$HOME/.parsar}/build/mcode-runtime" +``` + +Configure Core's existing managed Docker provider with the immutable image ID, +`deploy/codex/seccomp.json` and `nested_sandbox: true`. Core, database ownership, +enrollment and the public protocol remain shared. The image supplies the private +`PARSAR_MCODE_WORKSPACE=managed` and companion paths; caller Agent options cannot +change them. Public Files and Artifacts use the common bound workspace helpers. + +The native process and ACP Session use a private control directory, while six +original native tools execute against `/workspace` through one trusted MCP bridge +and the upstream Linux sandbox. MCP is internal transport here; it does not enable +caller-supplied public MCP servers. Project instructions must be read through the +workspace tools. Native automatic project configuration and diff/undo capture do +not apply to this isolated tool path. Exact native-ID continuation remains +required; recovery without a recorded ID fails closed. Native Bash observations +become public `command_execution` items after command arguments arrive. Their +text output and status are retained; absent native exit code/duration stay unknown. +The private MCP server and file/skill/task utilities are not invented public MCP +or function calls. + ## Provider configuration Keep `AGENTS_API_EXECUTION_OPTIONS_FILE` private (mode 0600). Supply the existing @@ -60,7 +96,8 @@ Each API Session uses a separate native state directory and cwd. The execution child inherits only process and model-network essentials; it does not inherit Core/daemon tokens or arbitrary Node startup configuration. The adapter owns its native config, instructions and home and disables external skills, delegated work, -web search, file/shell tools, browser tools, mcode-tools and native goals. +web search, builtin file/shell tools, browser tools, mcode-tools and native goals. +The workspace profile adds only its trusted isolated tool bridge. The native model may still see `skill`, `task_query`, `task_output` and `task_stop`. The first loads an exact registered skill name and cannot execute a script; the @@ -114,8 +151,10 @@ results or native history. Product ACP regression uses the same 0.4.12 package and covers new/resume, model/instruction refresh, Skill discovery and MCP using its existing synthetic provider fixture. -This acceptance uses an in-process Core HTTP server and a separate real daemon; -it is not a standalone Core deployment certification. Hosted Docker isolation, -Files/Artifacts and complete official protocol compatibility remain unqualified. -Before release, record `make check`, race checks and independent review alongside -these native results in the board task. +That text acceptance used an in-process Core HTTP server and a separate real +daemon. The subsequent [Docker workspace qualification](../../../../contracts/agents-api/mcode-workspace-v1.md) +uses independently built Core binaries, its own database and managed Runtime. +It covers public Files/Artifacts, cancellation, reconnect, exact-history recovery +and isolation with real Kimi and MiniMax APIs. Read its recorded Kimi continuation +limitation: new model-issued commands are not automatic recovery replay. Neither +acceptance establishes complete official protocol compatibility. diff --git a/services/agents-api/internal/engine/mcode.go b/services/agents-api/internal/engine/mcode.go index 38e98e80..e101a4df 100644 --- a/services/agents-api/internal/engine/mcode.go +++ b/services/agents-api/internal/engine/mcode.go @@ -9,14 +9,14 @@ import ( ) func mcodeProfile() Profile { - return Profile{Placements: []string{"none"}, ValidateConfiguration: func(a v1.Agent, e *v1.Environment, daemon bool) error { - if e == nil || e.Type != "none" || daemon || strings.TrimSpace(a.Model) == "" || a.MultiAgent.Enabled || a.MultiAgent.MaxConcurrentSubagents != nil || a.Reasoning.Effort != nil || a.Reasoning.Summary != nil || (a.ServiceTier != "" && a.ServiceTier != "auto") || (a.Text.Format.Type != "" && a.Text.Format.Type != "text") || (a.Text.Verbosity != "" && a.Text.Verbosity != "medium") { + return Profile{Placements: []string{"none", "openai_hosted"}, ValidateConfiguration: func(a v1.Agent, e *v1.Environment, daemon bool) error { + if e == nil || (e.Type != "none" && e.Type != "openai_hosted") || daemon || strings.TrimSpace(a.Model) == "" || a.MultiAgent.Enabled || a.MultiAgent.MaxConcurrentSubagents != nil || a.Reasoning.Effort != nil || a.Reasoning.Summary != nil || (a.ServiceTier != "" && a.ServiceTier != "auto") || (a.Text.Format.Type != "" && a.Text.Format.Type != "text") || (a.Text.Verbosity != "" && a.Text.Verbosity != "medium") { return ErrInvalidInput } return nil }, ValidateTools: func(_ *v1.Environment, _ bool, functions []proto.FunctionTool, mcp []proto.MCPHTTPServer) error { if len(functions) > 0 || len(mcp) > 0 { - return errors.New("The configured engine supports text execution without public tools.") + return errors.New("The configured engine does not support public functions or MCP tools.") } return nil }} diff --git a/services/agents-api/internal/execution/mcode_profile_test.go b/services/agents-api/internal/execution/mcode_profile_test.go index 7967aecd..8cbf8e5d 100644 --- a/services/agents-api/internal/execution/mcode_profile_test.go +++ b/services/agents-api/internal/execution/mcode_profile_test.go @@ -9,7 +9,7 @@ func TestMCodeOperationQualification(t *testing.T) { valid bool }{ {"text", `{"agent":{"model":"real-model"},"environment":{"type":"none"}}`, true}, - {"hosted", `{"agent":{"model":"real-model"},"environment":{"type":"openai_hosted"}}`, false}, + {"hosted", `{"agent":{"model":"real-model"},"environment":{"type":"openai_hosted"}}`, true}, {"verbosity", `{"agent":{"model":"real-model","text":{"verbosity":"high"}},"environment":{"type":"none"}}`, false}, {"subagents", `{"agent":{"model":"real-model","multi_agent":{"enabled":true}},"environment":{"type":"none"}}`, false}, {"functions", `{"agent":{"model":"real-model","tools":[{"type":"function","name":"f","parameters":{"type":"object"}}]},"environment":{"type":"none"}}`, false},